# Typing indicators Typing indicators enable you to display which users are currently writing a message in a room. This feature can be used to display a message such as *Sandi is typing...*, or when a certain threshold is reached you could instead display *Multiple people are typing...* or *12 people are typing...*. Typing events are emitted whenever a user starts or stops typing. ## Subscribe to typing events Subscribe to typing events by registering a listener. Typing events can be emitted when a user starts typing, and when they stop typing. Use the [`typing.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#subscribe)[`typing.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/subscribe%28%29-7uox7)[`typing.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/subscribe.html) method in a room to receive these updates: Use the [`collectAsCurrentlyTyping()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-currently-typing.html) extension function to observe typing changes reactively in Jetpack Compose: Subscribe to typing events with the [`useTyping`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/functions/chat-react.useTyping.html) hook. Supply an optional listener to receive the typing events, or use the [`currentlyTyping`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-react.UseTypingResponse.html#currentlyTyping) property returned by the hook to access the list of those users that are currently typing. ```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}") } ``` ```jetpack 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", }, "change": { "type": "typing.started", "clientId": "clemons" } } ``` The following are the properties of a typing event: | Property | Description | Type | |----------|-------------|------| | type | The type of the event. | String | | currentlyTyping | A set of all users currently typing. | Set | | change | The single change that resulted in the event. | Object | | | `type`: The type of change that occurred. | String | | | `clientId`: The `clientId` of the user that triggered the change. | String | 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 Use the `unsubscribe()` function returned in the `subscribe()` response to remove a typing listener: Jetpack Compose automatically handles lifecycle and cleanup when using `collectAsCurrentlyTyping()`. You don't need to handle removing listeners, as this is done automatically by the SDK. When you unmount the component that is using the `useTyping` hook, it will automatically handle unsubscribing any associated listeners registered for 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() ``` ## Set typing status Use the [`typing.keystroke()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#keystroke)[`typing.keystroke()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/keystroke%28%29)[`typing.keystroke()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/keystroke.html) method to emit a typing event with `type` set to `typing.started`. Use the [`keystroke()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-react.UseTypingResponse.html#keystroke) method available from the response of the `useTyping` hook to emit an event when a user has started typing. ```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() ``` ```jetpack 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") } ) } ```
Use the [`stop()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#stop)[`stop()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/stop%28%29)[`stop()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/stop.html) method to emit a typing event with `type` set to `typing.stopped`. Use the [`stop()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-react.UseTypingResponse.html#stop) method available from the response of the `useTyping` hook to emit an event when a user has stopped typing. ```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() ``` ```jetpack 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 Use the [`typing.current`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#current)[`typing.current`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/current)[`typing.current`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/current.html) property to retrieve a set of `clientId`s for all users that are currently typing in the room: ```javascript const currentlyTypingClientIds = room.typing.current; ``` ```swift let currentlyTypingClientIds = room.typing.current ``` ```kotlin val currentlyTypingClientIds = room.typing.current ``` ```jetpack val currentlyTypingClientIds = room.typing.current ``` Use the [`currentlyTyping`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-react.UseTypingResponse.html#currentlyTyping) property available from the response of the `useTyping` hook to view a list of all users that are currently typing in the room.