React 19 component library

Decisions are overrated.

Carefully composed shadcn/ui components for interfaces that need a little more personality than strictly necessary.

$pnpm add gra-ui

01 / Components

All of them.
For some reason.

The complete public surface of gra-ui, including the newest way to make an element explain its own children.

Button

A conventional button with the usual variants.

Read the example →
Signature
ButtonProps extends ComponentPropsWithoutRef<"button">
Props
variant, size, asChild, native button props
Example
<Button>Click</Button>

IndecisiveButton

A button that cycles through labels before accepting a decision.

Read the example →
Signature
IndecisiveButtonProps
Props
children, choices, interval, onDecision, Button props
Example
<IndecisiveButton>Decide</IndecisiveButton>

CountedClone

New

A clone that reports how many children its element already has.

Read the example →
Signature
CountedCloneProps
Props
element
Example
<CountedClone element={<span>One child</span>} />

02 / Component

IndecisiveButton

A button that considers every option before committing to the one under your cursor.

Preview Interactive

Hover, focus, then click. No pressure.

Controls

03 / Component

CountedClone

It counts an element's children and stores the answer on the element, where it will help nobody.

One childdata-child-count="1"

04 / Usage

One import.
Several opinions.

Import the compiled stylesheet once. Your application does not need Tailwind or additional source scanning.

example.tsxTSX
import { IndecisiveButton } from "gra-ui";
import "gra-ui/styles.css";

<IndecisiveButton
  choices={["Ship it", "Wait", "Ship it anyway"]}
  interval={900}
  onDecision={(choice) => console.log(choice)}
>
  Decide
</IndecisiveButton>

05 / API

Small surface area.

Native button props, refs, sizes and variants are forwarded. The component adds only what its indecision requires.

childrenReactNodeThe stable label shown while the button is idle.
choicesreadonly string[]Labels cycled while the button is active.
intervalnumberDelay between choices. Defaults to 900 ms.
onDecision(choice, index, event) => voidCalled with the visible choice on click.
variantButton variantAny shadcn Button visual variant.