### Javascript
```
const {unsubscribe} = room.typing.subscribe((event) => {
console.log("Currently typing: ", event.currentlyTyping);
});
```
### React
```
import { useTyping } from '@ably/chat/react';
import { TypingSetEvent } from '@ably/chat';
const MyComponent = () => {
const { currentlyTyping, roomError } = useTyping({
listener: (typingEvent: TypingSetEvent) => {
console.log('Typing event received: ', typingEvent);
},
});
return (
{roomError && Typing Error: {roomError.message}
}
Currently typing: {Array.from(currentlyTyping).join(', ')}
);
};
```
### Swift
```
let typingSubscription = room.typing.subscribe()
for await typing in typingSubscription {
typingInfo = typing.currentlyTyping.isEmpty ? "" :
"Typing: \(typing.currentlyTyping.joined(separator: ", "))..."
}
```
### Kotlin
```
val subscription = room.typing.subscribe { event: TypingSetEvent ->
println("currently typing: ${event.currentlyTyping}")
}
```
### Android
```
import androidx.compose.material.*
import androidx.compose.runtime.*
import com.ably.chat.Room
import com.ably.chat.extensions.compose.collectAsCurrentlyTyping
@Composable
fun TypingComponent(room: Room) {
val currentlyTyping by room.collectAsCurrentlyTyping()
Text("Currently typing: ${currentlyTyping.joinToString(", ")}")
}
```
### Typing event structure
The following is the structure of a typing event:
#### Json
```
{
"type": "typing.set.changed",
"currentlyTyping": ["clemons", "zoranges"],
"currentTypers": [
{ "clientId": "clemons", "userClaim": "{\"display_name\":\"Clem\"}" },
{ "clientId": "zoranges" }
],
"change": {
"type": "typing.started",
"clientId": "clemons",
"userClaim": "{\"display_name\":\"Clem\"}"
}
}
```
The following are the properties of a typing event:
| Property | Description | Type |
|----------|-------------|------|
| `type` | The type of the event. | `String` |
| `currentlyTyping` | A set of all `clientId`s currently typing. Deprecated: use `currentTypers` instead. | `Set` |
| `currentTypers` | An array of users currently typing, with associated metadata including `clientId` and `userClaim`. | `Array` |
| `change` | The single change that resulted in the event. | `Object` |
| `change.type` | The type of change that occurred. | `String` |
| `change.clientId` | The `clientId` of the user that triggered the change. | `String` |
| `change.userClaim` | A server-signed [user claim](https://ably.com/docs/chat/setup.md#user-claims) attached to this typing event, derived from the user's [JWT](https://ably.com/docs/chat/setup.md#set-user-claims). | `String` (optional) |
You can use the size of the `currentlyTyping` set to decide whether to display individual user names, or that multiple people are typing in your user interface.
### Unsubscribe from typing events
#### Javascript
```
// Initial subscription
import { TypingSetEvent } from '@ably/chat';
const { unsubscribe } = room.typing.subscribe((event: TypingSetEvent) => {
console.log('Typing event received: ', event);
});
// To remove the listener
unsubscribe();
```
#### Kotlin
```
// Initial subscription
val (unsubscribe) = room.typing.subscribe { event ->
println("Typing event received: $event")
}
// To remove the listener
unsubscribe()
```
### Javascript
```
await room.typing.keystroke();
```
### React
```
import { useTyping } from '@ably/chat/react';
const MyComponent = () => {
const { keystroke, currentlyTyping, roomError } = useTyping();
const handleKeystrokeClick = () => {
keystroke();
};
return (
{roomError && Typing Error: {roomError.message}
}
Currently typing: {Array.from(currentlyTyping).join(', ')}
);
};
```
### Swift
```
try await room.typing.keystroke()
```
### Kotlin
```
room.typing.keystroke()
```
### Android
```
import androidx.compose.material.*
import androidx.compose.runtime.*
import com.ably.chat.Room
import kotlinx.coroutines.launch
@Composable
fun TypingKeystrokeComponent(room: Room) {
val coroutineScope = rememberCoroutineScope()
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { newText ->
text = newText
coroutineScope.launch {
room.typing.keystroke()
}
},
label = { Text("Type a message") }
)
}
```
### Javascript
```
await room.typing.stop();
```
### React
```
import { useTyping } from '@ably/chat/react';
const MyComponent = () => {
const { stop, roomError } = useTyping();
const handleStopClick = () => {
stop();
};
return (
{roomError && Typing Error: {roomError.message}
}
);
};
```
### Swift
```
try await room.typing.stop()
```
### Kotlin
```
room.typing.stop()
```
### Android
```
import androidx.compose.material.*
import androidx.compose.runtime.*
import com.ably.chat.Room
import kotlinx.coroutines.launch
@Composable
fun StopTypingComponent(room: Room) {
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
coroutineScope.launch {
room.typing.stop()
}
}) {
Text("Stop Typing")
}
}
```
### Typing Event Frequency
The Typing feature includes a configurable timer that controls how often typing events are sent to the server. This timer is reset each time a new typing event is sent, it works as follows:
* On the **first call** to `keystroke()`, the timer is set and an event is sent to the server.
* **Subsequent calls** before the timer expires result in a no-op.
* After the timer expires, a new typing event is sent and the timer is reset.
* If `stop()` is called, the timer is reset and a `typing.stopped` event is sent to the server.
You can configure the length of this timer using the `heartbeatThrottleMs` parameter in `RoomOptions` (default: **10,000ms**).
It is recommended that you call `keystroke()` with every keypress, and the SDK will handle when and if to send a typing indicator to the server.
### Emulating User Behavior
You can emulate user behavior (e.g., in chatbots) by setting a timeout to call `keystroke()` at intervals equal to the `heartbeatThrottleMs` plus a small delay, e.g. 200ms. This will ensure the typing indicator remains active.
### Grace Period for Typing Events
For the recipient of typing events:
* The typing indicator remains active for the **duration** defined by the `heartbeatThrottleMs` parameter, plus a predefined **2000ms grace period**.
* Receiving a new typing event before the grace period ends will reset the timeout.
* If the grace period ends without receiving a new typing event, the SDK will emit a `typing.stopped` event for that client to any subscribed listeners.
**For example:** With the `heartbeatThrottleMs` set to **10,000ms**, the typing indicator remains active for **12,000ms**. If no new typing event is received within this time, the SDK will emit a `typing.stopped` event.
### Adjusting the Event Frequency
You can adjust the `heartbeatThrottleMs` parameter to balance responsiveness and resource costs:
* **Increase responsiveness**: Lower the value → More typing events are sent to the server → Higher cost as more messages are sent.
* **Save resource costs**: Raise the value → Fewer typing events are sent to the server → Lower responsiveness, but less cost as fewer messages are sent overall.
This balance allows you to optimize cost and responsiveness based on your applications needs.
## Retrieve a list of users that are currently typing
### Javascript
```
const currentlyTyping = room.typing.currentTypers;
```
### React
```
import { useTyping } from '@ably/chat/react';
const MyComponent = () => {
const { currentTypers } = useTyping();
return (
Currently typing: {currentTypers.map((typer) => typer.clientId).join(', ')}
);
};
```
### Swift
```
let currentlyTypingClientIds = room.typing.current
```
### Kotlin
```
val currentlyTypingClientIds = room.typing.current
```
### Android
```
val currentlyTypingClientIds = room.typing.current
```
## Related Topics
- [Messages](https://ably.com/docs/chat/rooms/messages.md): Send, update, delete, and receive messages in chat rooms.
- [Message history](https://ably.com/docs/chat/rooms/history.md): Retrieve previously sent messages from history.
- [Presence](https://ably.com/docs/chat/rooms/presence.md): Use presence to see which users are online and their user status.
- [Occupancy](https://ably.com/docs/chat/rooms/occupancy.md): Use occupancy to see how many users are in a room.
- [Message reactions](https://ably.com/docs/chat/rooms/message-reactions.md): React to chat messages
- [Room reactions](https://ably.com/docs/chat/rooms/reactions.md): Enable users to send reactions at the room level, based on what is happening in your application, such as a goal being scored in your livestream.
- [Share media](https://ably.com/docs/chat/rooms/media.md): Share media such as images, videos, or files in a chat room.
- [Message replies](https://ably.com/docs/chat/rooms/replies.md): Add reply functionality to messages in a chat room.
## Documentation Index
To discover additional Ably documentation:
1. Fetch [llms.txt](https://ably.com/llms.txt) for the canonical list of available pages.
2. Identify relevant URLs from that index.
3. Fetch target pages as needed.
Avoid using assumed or outdated documentation paths.