TerraUI v0.1.1

Button

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

iPhone 17 Pro · iOS 27
Five variants: default, secondary, outline, ghost and destructive, each a raised key over its edge.Five variants: default, secondary, outline, ghost and destructive, each a raised key over its edge.
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.

iPhone 17 Pro · iOS 27
Small, default and large keys: the label steps up with the key.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.

iPhone 17 Pro · iOS 27
Loading holds the key's width with a spinner; disabled is molded flat; selected sits pressed in its well.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.

iPhone 17 Pro · iOS 27
ButtonIcon takes a render function: the key hands your icon its colour and size.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, with accessibilityState keeping disabled, busy (while loading) and selected in sync. Your own accessibilityState is 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 feedback handler with "press", or "select" when selected is 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, never onClick, and style props, never className.
  • Put text inside ButtonText; a bare string child does not render.
  • Keep one default key per view. The actions around it step down to secondary, outline or ghost.
  • Label the action with a verb and its object: "Delete playlist", not "OK".
  • loading is yours to clear: the key stays busy until you set it back to false.
  • Confirm a destructive action that can't be undone in a Dialog first.

On this page