Skip to content
React NativeBeta

Button

Variants, sizes, icons and a loading state. An async onPress shows progress by itself.

Beta. React Native support is in beta. APIs may change or break between prereleases (npm tag native).

On this page
Web preview — real device may differ
tsx
import { useState } from "react";
import { Text } from "react-native";
import { Button } from "@/component-lib/button";
import { useKitTheme } from "@/component-lib/shared/native";
import { Row, Section } from "./layout"; // Section and Row: page layout for the demo
 
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
 
function Glyph({ children }: { children: string }) {
  const { tokens } = useKitTheme();
  return <Text style={{ fontSize: 16, color: tokens.accentFg, fontWeight: "700" }}>{children}</Text>;
}
 
export function ButtonDemo() {
  const [saved, setSaved] = useState(0);
  return (
    <>
      <Section title="Button · variants">
        <Row>
          <Button>Primary</Button>
          <Button variant="secondary">Secondary</Button>
          <Button variant="outline">Outline</Button>
        </Row>
        <Row>
          <Button variant="ghost">Ghost</Button>
          <Button variant="danger">Delete</Button>
          <Button variant="link">Link</Button>
        </Row>
      </Section>
 
      <Section title="Button · sizes and icons">
        <Row>
          <Button size="sm">Small</Button>
          <Button size="md">Medium</Button>
          <Button size="lg">Large</Button>
        </Row>
        <Row>
          <Button leading={<Glyph>+</Glyph>}>New invoice</Button>
          <Button icon={<Glyph>✓</Glyph>} aria-label="Confirm" />
          <Button disabled>Disabled</Button>
        </Row>
      </Section>
 
      <Section title="Button · loading">
        <Row>
          <Button loading>Saving</Button>
          <Button variant="secondary" loading loadingText="Uploading…">
            Upload
          </Button>
        </Row>
        <Button fullWidth onPress={() => wait(1500).then(() => setSaved((n) => n + 1))}>
          {`Async press · saved ${saved}×`}
        </Button>
      </Section>
    </>
  );
}

Install

npx @grampro/kit@native add Button

Usage

tsx
import { Button } from "@/component-lib/button";
 
<Button onPress={() => save()}>Save</Button>
<Button variant="outline" leading={<PlusIcon />}>New invoice</Button>
<Button variant="danger" loading>Deleting</Button>

Props

ButtonProps

PropTypeDescription
variantButtonVariantDefault primary.
sizeButtonSizeDefault md. Every size keeps a 44dp touch target.
loadingbooleanShow a spinner and ignore presses, keeping the button's width.
loadingTextReactNodeText shown next to the spinner while busy, instead of keeping the label.
leadingReactNodeIcon before the label.
trailingReactNodeIcon after the label.
iconReactNodeAn icon-only button. Give it an aria-label.
fullWidthbooleanFill the width of the container.
disabledboolean
onPress(event: GestureResponderEvent) => unknownReturn a promise to show the loading state until it settles.
childrenReactNode
styleStyleable<ViewStyle>Root style, or a function of the button's state.
stylesPartial<{ root: Styleable<ViewStyle>; content: Styleable<ViewStyle>; label: Styleable<T…
localeTextPartial<ButtonLocaleText>

Also accepts the props of Omit<PressableProps, "children" | "style" | "onPress" | "disabled">.

Native and web

  • onPress instead of onClick. Return a promise and the button shows its spinner until it settles.
  • Every size keeps a 44dp touch target (small buttons get hitSlop).
  • No type="submit" or form status: React Native has no forms.
  • Web version: Button on the web.