TerraUI v0.1.1

Icon button

A square Button for a single icon, for actions that a glyph names on its own. It always carries an accessibility label.

iPhone 17 Pro · iOS 27
The five Button variants on a square face: default, secondary, outline, ghost and destructive.The five Button variants on a square face: default, secondary, outline, ghost and destructive.
import Add01Icon from "@hugeicons-pro/core-bulk-rounded/Add01Icon";
import Delete02Icon from "@hugeicons-pro/core-bulk-rounded/Delete02Icon";
import FavouriteIcon from "@hugeicons-pro/core-bulk-rounded/FavouriteIcon";
import Settings01Icon from "@hugeicons-pro/core-bulk-rounded/Settings01Icon";
import Share01Icon from "@hugeicons-pro/core-bulk-rounded/Share01Icon";
import { HugeiconsIcon } from "@hugeicons/react-native";
import { ButtonIcon, IconButton } from "@terratcg/ui";
import { View } from "react-native";

export default function IconButtonVariants() {
  return (
    <View style={{ flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: 12 }}>
      <IconButton accessibilityLabel="Add">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Add01Icon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
      <IconButton variant="secondary" accessibilityLabel="Share">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Share01Icon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
      <IconButton variant="outline" accessibilityLabel="Favourite">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={FavouriteIcon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
      <IconButton variant="ghost" accessibilityLabel="Settings">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Settings01Icon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
      <IconButton variant="destructive" accessibilityLabel="Delete">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Delete02Icon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
    </View>
  );
}

Import

import { ButtonIcon, IconButton } from "@terratcg/ui";

Usage

<IconButton variant="ghost" accessibilityLabel="Settings" onPress={openSettings}>
  <ButtonIcon>{({ color, size }) => <SettingsIcon color={color} size={size} />}</ButtonIcon>
</IconButton>

IconButton is a Button with a square face: it takes the same variants, states and feedback, and only its sizes change. Hand it the icon through ButtonIcon, whose render function gives you the face's colour and the icon size. TerraUI ships no icons; bring your own.

Examples

Sizes

Small, default and large: 36, 44 and 52 point squares, the icon stepping up with the face.

iPhone 17 Pro · iOS 27
Small, default and large: 36, 44 and 52 point squares, the icon stepping up with the face.Small, default and large: 36, 44 and 52 point squares, the icon stepping up with the face.
import Add01Icon from "@hugeicons-pro/core-bulk-rounded/Add01Icon";
import { HugeiconsIcon } from "@hugeicons/react-native";
import { ButtonIcon, IconButton } from "@terratcg/ui";
import { View } from "react-native";

export default function IconButtonSizes() {
  return (
    <View style={{ flexDirection: "row", flexWrap: "wrap", justifyContent: "center", alignItems: "center", gap: 12 }}>
      <IconButton size="sm" accessibilityLabel="Add, small">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Add01Icon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
      <IconButton accessibilityLabel="Add">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Add01Icon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
      <IconButton size="lg" accessibilityLabel="Add, large">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Add01Icon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
    </View>
  );
}

States

Loading swaps the icon for a spinner at the same size; disabled is molded flat; selected sits pressed in its well.

iPhone 17 Pro · iOS 27
Loading swaps the icon for a spinner at the same size; disabled is molded flat; selected sits pressed in its well.Loading swaps the icon for a spinner at the same size; disabled is molded flat; selected sits pressed in its well.
import FavouriteIcon from "@hugeicons-pro/core-bulk-rounded/FavouriteIcon";
import Share01Icon from "@hugeicons-pro/core-bulk-rounded/Share01Icon";
import { HugeiconsIcon } from "@hugeicons/react-native";
import { ButtonIcon, IconButton } from "@terratcg/ui";
import { View } from "react-native";

export default function IconButtonStates() {
  return (
    <View style={{ flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: 12 }}>
      <IconButton loading accessibilityLabel="Saving">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Share01Icon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
      <IconButton disabled accessibilityLabel="Share">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={Share01Icon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
      <IconButton variant="outline" selected accessibilityLabel="Favourite">
        <ButtonIcon>{({ color, size }) => <HugeiconsIcon icon={FavouriteIcon} color={color} size={size} />}</ButtonIcon>
      </IconButton>
    </View>
  );
}

Props

Prop

Type

IconButton also accepts React Native's Pressable props, such as hitSlop and testID. size maps to Button's square sizes: sm is icon-sm, default is icon and lg is icon-lg. selected and tint work together as on Button.

Accessibility

  • Role: button, with accessibilityState keeping disabled, busy (while loading) and selected in sync, as on Button.
  • Label: accessibilityLabel is required: the face shows no text, so the label is the only name a screen reader has. Name the action ("Share card"), not the glyph ("Arrow").
  • Touch target: at least 44 pt on iOS and 48 dp on Android. The 36 pt sm face keeps its size and grows its touch area with hitSlop.
  • Reduce Motion: the face no longer travels when pressed; the press tint stays.

Platform differences

  • iOS and Android: the same square key and the same travel, with no ripple. Only the minimum touch target differs: 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.
  • Web: IconButton renders through react-native-web for these docs, but the web is not a supported target.

Rules

  • Use an IconButton only when the icon is unmistakable on its own; otherwise use a Button with a ButtonText.
  • Always pass accessibilityLabel, and keep it in step with what the icon means.
  • Put one icon inside ButtonIcon, and draw it with the colour and size the render function hands you.
  • Use onPress, never onClick, and style props, never className.
  • loading is yours to clear: the key stays busy until you set it back to false.

On this page