Buttons and inputs
The SDK draws its buttons and text fields with a few shared components. Using them in your own screens keeps the colors, sizes, press states and right-to-left behavior consistent with the SDK. Every component on this page is exported from stream-chat-react-native and stream-chat-expo, and none has an override key.
Best practices
- Give an icon-only
Buttonan accessibility label, withaccessibilityLabeloraccessibilityLabelKey. It has no text to announce. Buttonfills the width of its container unless it'siconOnly. Passstyle={{ width: "auto" }}for a button as wide as its label.- Pass icons as
LeadingIconandTrailingIcon, not insidelabel. The button sizes and colors them, and swaps them in a right-to-left layout. - Keep
Input'sstateand messages in your own state, and setstate="error"with anerrorMessagewhen validation fails. On Android, TalkBack announces the message. - Every component here reads the theme, which
OverlayProviderandChatprovide.
Available components
| Component | What it is | Where the SDK renders it |
|---|---|---|
Button |
A pressable pill with a label, icons, or both | The composer's buttons, the message menu, the image gallery, notifications, polls and channel details |
Input |
A text field with an optional title, icons and a helper line | SearchInput, the channel name in the edit form, and the poll creation form |
SearchInput |
An Input with a search icon and a clear button |
The member list, the add members form and the pinned messages list of channel details |
SpeedSettingsButton |
The playback rate, such as x1.5 |
Audio attachments and the image gallery's video controls |
GiphyChip |
The composer's active command, with a button that clears it | The composer's text field, while a command such as /giphy is set |
Button
Button is a React Native Gesture Handler Pressable inside a rounded View. variant and type pick its colors from the button* semantic tokens:
type |
Looks like |
|---|---|
solid |
Filled with the variant's background color |
outline |
A 1-point border in the variant's border color |
ghost |
No background and no border |
A pressed button gets semantics.backgroundUtilityPressed, and a selected one semantics.backgroundUtilitySelected. A disabled one uses the disabled colors.
ButtonProps are the props of the Gesture Handler Pressable, which go to it, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
'primary' | 'secondary' | 'destructive' |
required | The color set. |
type |
'solid' | 'outline' | 'ghost' | 'liquidGlass' |
required | The fill. 'liquidGlass' has no styles, and a button of that type throws when it renders. |
size |
'sm' | 'md' | 'lg' |
'md' |
32, 40 or 48 points tall. An iconOnly button is as wide as it's tall. |
label |
React.ReactNode |
The content. A string is wrapped in a Text in the button's color; other nodes render as they are. |
|
LeadingIcon |
IconRenderer |
The icon before the label. It gets a 20-point size, a 1.5 stroke width and the button's color as stroke. |
|
TrailingIcon |
IconRenderer |
The icon after the label. The two swap sides in a right-to-left layout. | |
iconOnly |
boolean |
false |
Renders only LeadingIcon, or TrailingIcon without one, in a square button. |
selected |
boolean |
false |
Shows the selected background and reports the selected state to screen readers. |
accessibilityLabelKey |
string |
A translation key for the label. With SDK accessibility on, its translation replaces accessibilityLabel. |
|
accessibilityLabelParams |
Record<string, unknown> |
The values for accessibilityLabelKey. |
|
style |
StyleProp<ViewStyle> |
Applied to the outer View, after the size. |
disabled and onLayout come from Pressable. disabled defaults to false, and onLayout goes to the outer View. IconRenderer is (props: IconProps) => React.ReactNode, so the icons of ComponentsContext can be passed as they are.
Input
Input is a React Native TextInput with a frame around it. It forwards its ref to the TextInput. Above the field it shows title and description, and below it a helper line that depends on state:
state |
The helper line shows |
|---|---|
'default' |
An info icon and infoText |
'error' |
An info icon and errorMessage, in the error color |
'success' |
A check mark and successMessage, in the success color |
While state is 'error', errorMessage is also announced through an assertive live region, even with helperText off. accessibilityLiveRegion is Android only, so VoiceOver on iOS doesn't announce it. The border is semantics.borderUtilityActive while the field is focused and semantics.borderUtilityDisabled while it isn't editable.
InputProps are the props of TextInput, which go to it, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
'outline' | 'ghost' |
required | 'outline' draws a 1-point border; 'ghost' draws none. |
title |
string |
The label above the field. It's also the field's accessibility label, unless you pass accessibilityLabel. |
|
description |
string |
A line under the title. It's also the field's accessibility hint. | |
LeadingIcon |
IconRenderer |
An icon at the start of the field. | |
TrailingIcon |
IconRenderer |
An icon at the end of the field. The two swap sides in a right-to-left layout. | |
state |
'default' | 'error' | 'success' |
'default' |
Picks the helper line and its color. |
helperText |
boolean |
true |
Shows the helper line. |
errorMessage |
string |
The helper text in the 'error' state. |
|
successMessage |
string |
The helper text in the 'success' state. |
|
infoText |
string |
The helper text in the 'default' state. |
|
containerStyle |
StyleProp<ViewStyle> |
Applied to the frame around the field and its icons. |
editable defaults to true. A style you pass replaces the field's default text style instead of adding to it, so set the font size and color you need in it.
SearchInput
SearchInput renders an Input with a search icon, the placeholder "Search", no helper line, words capitalized and autocorrect off. Once the user has typed, a clear button shows; it empties the field and calls onChangeText(""). It sits in a View with padding on every side.
SearchInputProps is Partial<InputProps>. Every prop you pass goes to the Input and replaces the default, except onChangeText, which SearchInput wraps to track whether the field has text.
SpeedSettingsButton
SpeedSettingsButton shows the rate with one decimal, such as x1.0 or x1.5, in a 40 by 24 point pill. It doesn't change the rate itself. Your onPress does.
| Prop | Type | Default | Description |
|---|---|---|---|
currentPlaybackRate |
number |
required | The rate to show. |
onPress |
() => void |
required | Called when the button is pressed. |
containerStyle |
StyleProp<ViewStyle> |
Applied to the pill. |
The button has no accessibility label or role. Wrap it, or set them through a component of your own, when it must be reachable with a screen reader.
GiphyChip
GiphyChip takes no props. It reads the composer from useMessageComposer(), so it renders inside a Channel or a Thread. It shows the name of the text composer's command in capitals, after a lightning icon, and a cross that calls TextComposer.clearCommand(). The composer's text field renders it while a command is set.
Example
A form with a validated field, a search field, and a full-width and an icon-only button:
import { useState } from "react";
import { View } from "react-native";
import {
Button,
Input,
SearchInput,
useComponentsContext,
} from "stream-chat-react-native";
export const RenameForm = ({
onFilter,
onSave,
}: {
onFilter: (query: string) => void;
onSave: (name: string) => void;
}) => {
const { icons } = useComponentsContext();
const [name, setName] = useState("");
const tooLong = name.length > 40;
return (
<View style={{ gap: 12, padding: 16 }}>
<SearchInput onChangeText={onFilter} placeholder="Find a channel" />
<Input
errorMessage="Use 40 characters or fewer"
infoText="Everyone in the channel sees the name"
onChangeText={setName}
state={tooLong ? "error" : "default"}
title="Channel name"
value={name}
variant="outline"
/>
<View style={{ flexDirection: "row", gap: 8 }}>
<Button
disabled={!name || tooLong}
label="Save"
onPress={() => onSave(name)}
style={{ flex: 1 }}
type="solid"
variant="primary"
/>
<Button
accessibilityLabel="Clear name"
iconOnly
LeadingIcon={icons.Cross}
onPress={() => setName("")}
type="outline"
variant="secondary"
/>
</View>
</View>
);
};Related
- Overriding components: the icons of
ComponentsContextand theIconPropsthey take. - Lists and indicators:
EmptySearchResult, which the SDK shows underSearchInputwhen nothing matches. - Accessibility: turning on the SDK's accessibility layer, which
accessibilityLabelKeydepends on.