Button
A raised key that runs an action when pressed. Compose its label with ButtonText and an optional ButtonIcon.


import { Button, ButtonText } from "@terratcg/ui";
import { View } from "react-native";
export default function ButtonVariants() {
return (
<View style={{ flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: 12 }}>
<Button>
<ButtonText>Default</ButtonText>
</Button>
<Button variant="secondary">
<ButtonText>Secondary</ButtonText>
</Button>
<Button variant="outline">
<ButtonText>Outline</ButtonText>
</Button>
<Button variant="ghost">
<ButtonText>Ghost</ButtonText>
</Button>
<Button variant="destructive">
<ButtonText>Delete</ButtonText>
</Button>
</View>
);
}Import
import { Button, ButtonIcon, ButtonText } from "@terratcg/ui";Usage
<Button onPress={save}>
<ButtonText>Save</ButtonText>
</Button>A Button hugs its label; pass block to stretch it across its parent. Put text inside ButtonText: a bare string child does not render on React Native.
Examples
Sizes
Small, default and large keys: the label steps up with the key.


import { Button, ButtonText } from "@terratcg/ui";
import { View } from "react-native";
export default function ButtonSizes() {
return (
<View style={{ flexDirection: "row", flexWrap: "wrap", justifyContent: "center", alignItems: "center", gap: 12 }}>
<Button size="sm">
<ButtonText>Small</ButtonText>
</Button>
<Button>
<ButtonText>Default</ButtonText>
</Button>
<Button size="lg">
<ButtonText>Large</ButtonText>
</Button>
</View>
);
}States
Loading holds the key's width with a spinner; disabled is molded flat; selected sits pressed in its well.


import { Button, ButtonText } from "@terratcg/ui";
import { View } from "react-native";
export default function ButtonStates() {
return (
<View style={{ flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: 12 }}>
<Button loading>
<ButtonText>Saving</ButtonText>
</Button>
<Button disabled>
<ButtonText>Disabled</ButtonText>
</Button>
<Button variant="outline" selected>
<ButtonText>Selected</ButtonText>
</Button>
</View>
);
}With an icon
ButtonIcon takes a render function: the key hands your icon its colour and size.


import Add01Icon from "@hugeicons-pro/core-bulk-rounded/Add01Icon";
import Delete02Icon from "@hugeicons-pro/core-bulk-rounded/Delete02Icon";
import { HugeiconsIcon } from "@hugeicons/react-native";
import { Button, ButtonIcon, ButtonText } from "@terratcg/ui";
import { View } from "react-native";
export default function ButtonWithIcon() {
return (
<View style={{ flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: 12 }}>
<Button>
<ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Add01Icon} color={color} size={size} />}</ButtonIcon>
<ButtonText>Add</ButtonText>
</Button>
<Button variant="destructive">
<ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Delete02Icon} color={color} size={size} />}</ButtonIcon>
<ButtonText>Delete</ButtonText>
</Button>
</View>
);
}Props
Button
Prop
Type
Button also accepts React Native's Pressable props, such as accessibilityLabel, hitSlop and testID. onPress receives the native press event, and a ref points at the underlying Pressable view. selected and tint work together: tint only shows on a selected secondary, outline or ghost key.
ButtonText
Prop
Type
ButtonText also accepts React Native's Text props, such as numberOfLines. Its size and colour come from the Button it sits in.
ButtonIcon
Prop
Type
useButtonContext
A label or icon of your own inside a Button reads the key it sits on with useButtonContext(): its variant, size, foregroundColor, iconSize, textVariant and its disabled, selected and loading states. Draw with foregroundColor and iconSize so your part matches ButtonText and ButtonIcon. It throws outside a Button.
Accessibility
- Role:
button, withaccessibilityStatekeepingdisabled,busy(whileloading) andselectedin sync. Your ownaccessibilityStateis merged on top. - Label: the ButtonText string. A key without text, such as an icon-only key, needs an
accessibilityLabel. - Touch target: at least 44 pt on iOS and 48 dp on Android. A smaller key keeps its face and grows its touch area with
hitSlop. - Larger text: under enlarged system text the label wraps and the face grows with it, so the action stays readable instead of clipping.
- Reduce Motion: the face no longer travels when pressed; the press tint stays.
Platform differences
- iOS and Android: the same key, the same travel and no ripple. The minimum touch target follows each platform: 44 pt on iOS, 48 dp on Android.
- Feedback: a press calls ThemeProvider's
feedbackhandler with"press", or"select"whenselectedis set. TerraUI plays no haptic or sound itself; your handler decides. - Web: Button renders through react-native-web for these docs, but the web is not a supported target.
Rules
- Use
onPress, neveronClick, and style props, neverclassName. - Put text inside
ButtonText; a bare string child does not render. - Keep one
defaultkey per view. The actions around it step down tosecondary,outlineorghost. - Label the action with a verb and its object: "Delete playlist", not "OK".
loadingis yours to clear: the key stays busy until you set it back tofalse.- Confirm a
destructiveaction that can't be undone in a Dialog first.