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 ButtonUsage
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
| Prop | Type | Description |
|---|---|---|
variant | ButtonVariant | Default primary. |
size | ButtonSize | Default md. Every size keeps a 44dp touch target. |
loading | boolean | Show a spinner and ignore presses, keeping the button's width. |
loadingText | ReactNode | Text shown next to the spinner while busy, instead of keeping the label. |
leading | ReactNode | Icon before the label. |
trailing | ReactNode | Icon after the label. |
icon | ReactNode | An icon-only button. Give it an aria-label. |
fullWidth | boolean | Fill the width of the container. |
disabled | boolean | |
onPress | (event: GestureResponderEvent) => unknown | Return a promise to show the loading state until it settles. |
children | ReactNode | |
style | Styleable<ViewStyle> | Root style, or a function of the button's state. |
styles | Partial<{ root: Styleable<ViewStyle>; content: Styleable<ViewStyle>; label: Styleable<T… | |
localeText | Partial<ButtonLocaleText> |
Also accepts the props of Omit<PressableProps, "children" | "style" | "onPress" | "disabled">.
Native and web
onPressinstead ofonClick. 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.