@renge-ui/react
Components
44 components built on the token system. Proportional. Accessible. Composable. No class names — every style references a Renge CSS variable.
pnpm add @renge-ui/tokens @renge-ui/reactStack
Flexbox container. Composes vertical and horizontal layouts using Fibonacci spacing tokens.
Vertical (default)
Horizontal + justify between
Horizontal + align center
<Stack gap="4">
<div>First</div>
<div>Second</div>
<div>Third</div>
</Stack>
<Stack direction="horizontal" justify="between">
<Text>Left</Text>
<Text>Right</Text>
</Stack>
{/* Flex wrap via style prop */}
<Stack direction="horizontal" gap="3" style={{ flexWrap: "wrap" }}>
<Badge>one</Badge>
<Badge>two</Badge>
<Badge>three</Badge>
</Stack>| Prop | Type | Default | Description |
|---|---|---|---|
| gap | "0" – "10" | "3" | Gap between children — Fibonacci spacing step (e.g. step 4 = 20px). |
| direction | "vertical" | "horizontal" | "vertical" | flex-direction: column or row. |
| align | "start" | "center" | "end" | "stretch" | "stretch" | align-items value. |
| justify | "start" | "center" | "end" | "between" | "around" | "start" | justify-content value. |
| as | ElementType | "div" | Rendered HTML element (div, ul, nav, etc.). |
Grid
CSS grid container with Fibonacci gap tokens. Accepts a number of equal columns or a custom template string.
3-column equal grid
Golden ratio split (1fr 1.618fr)
<Grid columns={3} gap="3">
<div>1</div>
<div>2</div>
<div>3</div>
</Grid>
{/* Custom template — e.g. golden ratio split */}
<Grid columns="1fr 1.618fr" gap="4">
<div>Narrow</div>
<div>Wide (φ)</div>
</Grid>
{/* Per-axis gaps */}
<Grid columns={2} gapX="5" gapY="3">
...
</Grid>| Prop | Type | Default | Description |
|---|---|---|---|
| columns | number | string | 1 | Number of equal columns, or a CSS grid-template-columns string. |
| rows | number | string | — | Row count or grid-template-rows string. |
| gap | "0" – "6" | "3" | Gap between all cells. |
| gapX | "0" – "6" | — | Column gap — overrides gap. |
| gapY | "0" – "6" | — | Row gap — overrides gap. |
| align | "start" | "center" | "end" | "stretch" | "stretch" | align-items value. |
| justify | "start" | "center" | "end" | "stretch" | "stretch" | justify-items value. |
Section
Page section wrapper with max-width constraint and automatic horizontal centering.
Centered with md max-width
<Section maxWidth="lg" paddingY="8" paddingX="5">
<Heading>Page content</Heading>
<Text>Body copy sits inside the max-width container.</Text>
</Section>
{/* Full-bleed with internal constraint */}
<div style={{ background: "var(--renge-color-bg-subtle)" }}>
<Section maxWidth="xl" paddingY="7">
<Heading>Wide section</Heading>
</Section>
</div>| Prop | Type | Default | Description |
|---|---|---|---|
| maxWidth | "sm" | "md" | "lg" | "xl" | "full" | "none" | "lg" | sm=640px · md=768px · lg=1024px · xl=1280px · full=100% · none=unset. |
| paddingX | "0" – "8" | "4" | Horizontal padding. |
| paddingY | "0" – "8" | "6" | Vertical padding. |
| padding | "0" – "8" | — | Sets both axes at once. Overrides paddingX and paddingY. |
| center | boolean | true | Applies margin-inline: auto to center the container. |
| animation | AnimationName | — | Renge animation token. |
| as | ElementType | "section" | Rendered HTML element. |
Container
Centered max-width wrapper. Max-widths follow Fibonacci-proportioned breakpoints: 520, 768, 1024, 1440. Horizontal padding defaults to space-5 (32px = Fibonacci×8×4).
Sizes (shown at reduced scale)
{/* Default: lg (1024px), px=5 */}
<Container>
<Heading>Page content</Heading>
</Container>
{/* Narrow — article width */}
<Container size="sm" px="4">
<Text>Prose text with narrow measure.</Text>
</Container>
{/* Custom padding */}
<Container size="xl" px="6">
<Grid columns={3} gap="5">…</Grid>
</Container>| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "xl" | "full" | "lg" | Max-width: 520 / 768 / 1024 / 1440 / 100%. |
| px | "0" – "6" | "5" | Horizontal padding — Fibonacci spacing token. |
AspectRatio
Ratio-maintaining container. Default ratio = φ (1.618…) — the golden ratio. Any content inside fills the proportioned box. Uses padding-bottom technique for universal support.
Common ratios
import { PHI } from "@renge-ui/tokens";
{/* Default: PHI (golden ratio) */}
<AspectRatio>
<img src="/photo.jpg" alt="…" style={{ objectFit: "cover" }} />
</AspectRatio>
{/* 16:9 video */}
<AspectRatio ratio={16/9}>
<video src="/demo.mp4" controls />
</AspectRatio>
{/* Square */}
<AspectRatio ratio={1}>
<div>…</div>
</AspectRatio>| Prop | Type | Default | Description |
|---|---|---|---|
| ratio | number | PHI (1.618…) | Width-to-height ratio. Pass 16/9 for widescreen, 1 for square, PHI for golden ratio. |
Spacer
Explicit whitespace element. All sizes map directly to Fibonacci spacing tokens — the visible grammar of the space between things.
Vertical spacers (rendered with borders for visibility)
<Heading>Section title</Heading>
<Spacer size="5" />
<Text>The proportion between the heading and the body text is deliberate.</Text>
{/* Horizontal spacer in flex layouts */}
<Stack direction="horizontal">
<Text>Left</Text>
<Spacer size="4" axis="horizontal" />
<Text>Right</Text>
</Stack>| Prop | Type | Default | Description |
|---|---|---|---|
| size | "1" – "10" | "4" | Fibonacci spacing step. size=4 = space-4 = 20px (Fibonacci 5 × 4px). |
| axis | "vertical" | "horizontal" | "vertical" | vertical=height · horizontal=width (for use in flex rows). |
Heading
Semantic heading using the PHI-derived type scale. Level maps to h1–h6 and sets a proportional default size.
Levels 1–4 with default sizing
Level 1 heading
h1 → 3xlLevel 2 heading
h2 → 2xlLevel 3 heading
h3 → xlLevel 4 heading
h4 → lgColor variants
Default — var(--renge-color-fg)
Subtle — var(--renge-color-fg-subtle)
Accent — var(--renge-color-accent)
<Heading level={1}>The golden ratio.</Heading>
<Heading level={2} color="accent">Section title</Heading>
{/* Override the default size for a level */}
<Heading level={3} size="2xl">Larger h3</Heading>
{/* PHI-derived line heights apply automatically */}
{/* h1/h2 → display (1.236), h3/h4 → heading (1.382) */}| Prop | Type | Default | Description |
|---|---|---|---|
| level | 1 | 2 | 3 | 4 | 5 | 6 | 2 | Semantic HTML level (h1–h6). Also sets the default size. |
| size | "lg" | "xl" | "2xl" | "3xl" | "4xl" | level-mapped | Override the default size. h1→3xl, h2→2xl, h3→xl, h4/5/6→lg. |
| color | "fg" | "fg-subtle" | "accent" | "fg" | Text color semantic token. |
| animation | AnimationName | — | Renge animation token. |
Text
Inline or block text element with full access to the PHI type scale, color tokens, and weight options.
Size scale
Colors + weights
<Text size="lg" color="fg-subtle">Natural proportion.</Text>
<Text as="p" weight="medium" size="base">
Fibonacci spacing.
</Text>
<Text size="sm" color="accent" weight="semibold">
PHI = 1.618033...
</Text>
{/* Block-level text */}
<Text as="p" size="base" color="fg-subtle">
Paragraph text using the base size — 16px, line-height 1.618 (φ).
</Text>| Prop | Type | Default | Description |
|---|---|---|---|
| size | "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "base" | Font size token. Steps derived from PHI: base × φ^n. |
| color | "fg" | "fg-subtle" | "fg-muted" | "accent" | "success" | "warning" | "danger" | "fg" | Text color semantic token. |
| weight | "normal" | "medium" | "semibold" | "bold" | "normal" | Font weight: 400 / 500 / 600 / 700. |
| align | "left" | "center" | "right" | — | Text alignment. |
| animation | AnimationName | — | Renge animation token. |
| as | ElementType | "span" | Rendered HTML element. |
Divider
Horizontal or vertical rule rendered as an <hr>. Uses border color tokens for consistent visual weight.
Horizontal variants
Between sections
Below — stronger border, tighter spacing
Vertical
Right content
<Divider />
<Divider color="border" spacing="2" />
<Divider orientation="vertical" spacing="4" />| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | "horizontal" | "vertical" | "horizontal" | Horizontal: 1px height, full width. Vertical: 1px width, full height. |
| spacing | "0" – "6" | "3" | Margin around the rule (marginBlock or marginInline). |
| color | "border" | "border-subtle" | "border-subtle" | Line color token. |
Anchor
Styled anchor element. Three variants for different contexts. Named Anchor (not Link) to avoid collision with Next.js's Link component.
Variants
<Anchor href="/docs">Read the docs</Anchor>
<Anchor href="/github" variant="subtle">GitHub →</Anchor>
{/* Always underlined */}
<Anchor href="/privacy" underline>
Privacy policy
</Anchor>
{/* In prose — inherits parent text color */}
<p>
Built on <Anchor href="/phi" variant="plain">natural mathematics</Anchor>.
</p>| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "subtle" | "plain" | "default" | default=accent color · subtle=fg-subtle · plain=inherit. |
| underline | boolean | false | Shows underline at rest (not just on hover). |
Input
Text input with Renge sizing and validation state styling. Focus ring is handled with inline event handlers to stay in-system.
States
Sizes
<Input placeholder="Enter value" size="md" />
<Input state="error" placeholder="Invalid input" fullWidth />
<Input state="success" defaultValue="Confirmed" fullWidth />
{/* Controlled */}
<Input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Controlled input"
fullWidth
/>size attribute (a number controlling character width) is excluded to avoid collision with Renge's size prop. Use style={{ width: "..." }} or fullWidth for sizing.| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | Controls padding and font size. |
| state | "default" | "error" | "success" | "default" | Sets border color to the corresponding semantic token. |
| fullWidth | boolean | false | Stretches input to 100% container width. |
Select
Styled native select element. Chevron rendered as a background SVG — no extra DOM nodes. Sizing and state tokens match Input exactly.
Sizes
States + placeholder
<Select placeholder="Choose a profile…" fullWidth>
<option value="ocean">Ocean</option>
<option value="earth">Earth</option>
</Select>
<Select state="error" fullWidth>
<option>Invalid</option>
</Select>
{/* Controlled */}
<Select value={profile} onChange={(e) => setProfile(e.target.value)}>
<option value="ocean">Ocean</option>
<option value="twilight">Twilight</option>
</Select>| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | Controls padding and font size — mirrors Input sizing. |
| state | "default" | "error" | "success" | "default" | Border color mapped to semantic state token. |
| fullWidth | boolean | false | Stretches to 100% container width. |
| placeholder | string | — | Renders a hidden disabled option as the default selection prompt. |
Checkbox
Custom-styled checkbox. The box and check geometry derive from Fibonacci spacing. The checkmark animates in with spring easing via a stroke-dashoffset draw.
States
Sizes
Controlled
<Checkbox label="Accept terms" />
<Checkbox label="Remember me" defaultChecked />
<Checkbox label="Partially selected" indeterminate />
{/* Controlled */}
<Checkbox
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
label="I agree to the terms"
/>| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | — | Label rendered beside the checkbox. |
| size | "sm" | "md" | "lg" | "md" | Box size — 16 / 20 / 24 px (Fibonacci-aligned). |
| indeterminate | boolean | false | Shows a dash instead of a checkmark — used for select-all states. |
| disabled | boolean | false | Reduces opacity and sets cursor:not-allowed. |
| checked | boolean | — | Controlled checked state. |
| defaultChecked | boolean | false | Uncontrolled initial value. |
Radio + RadioGroup
Radio button group. The inner dot is sized at outer / φ — the golden proportion is visible in every interaction. RadioGroup manages shared name and value state.
Basic group
Horizontal + sizes
Disabled
<RadioGroup
name="theme"
defaultValue="ocean"
onChange={(value) => setTheme(value)}
>
<Radio value="ocean" label="Ocean" />
<Radio value="earth" label="Earth" />
<Radio value="twilight" label="Twilight" />
</RadioGroup>
{/* Horizontal */}
<RadioGroup name="size" direction="horizontal" gap="4">
<Radio value="sm" label="Small" />
<Radio value="lg" label="Large" />
</RadioGroup>| Prop | Type | Default | Description |
|---|---|---|---|
| name | string | — | (RadioGroup) Shared input name — required for accessibility. |
| value | string | — | (RadioGroup) Controlled selected value. |
| defaultValue | string | "" | (RadioGroup) Uncontrolled initial value. |
| onChange | (value: string) => void | — | (RadioGroup) Called with the new value when selection changes. |
| direction | "vertical" | "horizontal" | "vertical" | (RadioGroup) Stack direction. |
| disabled | boolean | false | (RadioGroup) Disables all child radios. |
| value | string | — | (Radio) The value this radio represents. |
| label | string | — | (Radio) Label text. |
| size | "sm" | "md" | "lg" | inherited | (Radio) Individual size override. |
Switch
Toggle switch. Track width:height ≈ φ:1 — the golden proportion. Thumb slides with spring easing. Works as a styled checkbox under the hood for full accessibility.
Sizes
Label positions + controlled
<Switch label="Dark mode" />
<Switch label="Notifications" defaultChecked />
{/* Controlled */}
<Switch
label="Feature flag"
checked={enabled}
onChange={(e) => setEnabled(e.target.checked)}
/>
{/* Label on left */}
<Switch label="Auto-save" labelPosition="left" />| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | sm: 32×20px · md: 40×24px · lg: 48×28px. Track width≈φ×height. |
| label | string | — | Visible label text. |
| labelPosition | "left" | "right" | "right" | Label placement relative to the switch track. |
| checked | boolean | — | Controlled checked state. |
| defaultChecked | boolean | false | Uncontrolled initial state. |
| disabled | boolean | false | Dims and disables interaction. |
Textarea
Multi-line text input. Min-height steps follow Fibonacci line counts — 3, 5, and 8 lines at sm/md/lg respectively.
Sizes
States + resize
<Textarea placeholder="Describe your system…" fullWidth />
<Textarea
size="lg"
state="error"
placeholder="Too long — 500 characters max"
fullWidth
/>
{/* Controlled */}
<Textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
resize="none"
fullWidth
/>| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | Affects padding, font size, and minimum height (Fibonacci line counts: 3/5/8). |
| state | "default" | "error" | "success" | "default" | Border color state — mirrors Input. |
| fullWidth | boolean | false | Stretches to 100% container width. |
| resize | "none" | "vertical" | "horizontal" | "both" | "vertical" | CSS resize property. |
Slider
Range slider with optional φ markers. The golden section points (0.382 and 0.618 of the range) are marked as optional visual guides — the phi ratio embedded in every use.
Default + controlled
φ markers at 0.382 and 0.618
<Slider label="Variance" showValue defaultValue={0} />
{/* PHI markers — shows golden section at 0.382 and 0.618 */}
<Slider
label="Scale ratio"
showPhiMarkers
showValue
min={1}
max={2}
step={0.001}
defaultValue={1.618}
/>
{/* Controlled */}
<Slider
value={scale}
onChange={(e) => setScale(parseFloat(e.target.value))}
/>| Prop | Type | Default | Description |
|---|---|---|---|
| showPhiMarkers | boolean | false | Renders tick marks at 0.382 and 0.618 of the range — the golden section points. |
| label | string | — | Label text shown above the track. |
| showValue | boolean | false | Displays current value to the right of the label. |
| min / max / step | number | 0 / 100 / 1 | Native range input attributes. |
FormField
Wraps any input with a label, optional helper text, and error text. Error overrides helper when both are present.
States
<FormField
label="Email"
htmlFor="email"
helperText="We'll never share this."
>
<Input id="email" type="email" fullWidth />
</FormField>
<FormField
label="Username"
htmlFor="username"
required
errorText="Username already taken."
>
<Input id="username" state="error" fullWidth />
</FormField>| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | — | Label text rendered in a <label> element. |
| htmlFor | string | — | Links the label to an input via its id. |
| helperText | string | — | Muted hint text below the input. |
| errorText | string | — | Danger-colored error text. Overrides helperText when both are set. |
| required | boolean | — | Appends a red asterisk to the label. |
Card
Surface container with three visual variants. Padding and radius are token-driven.
Variants
Padding scale
<Card variant="elevated" padding="5" radius="3">
<Heading level={3}>Card title</Heading>
<Text color="fg-subtle">Supporting copy goes here.</Text>
</Card>
<Card variant="outlined" padding="4">
<Stack gap="3">
<Badge variant="success">Active</Badge>
<Heading level={3}>Status card</Heading>
</Stack>
</Card>| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "elevated" | "outlined" | "filled" | "elevated" | elevated: drop shadow · outlined: border · filled: bg-subtle background. |
| padding | "0" – "6" | "4" | Internal padding using Fibonacci spacing tokens. |
| radius | "none" | "1" – "5" | "full" | "3" | Border radius token (radius-3 = 12px by default). |
| animation | AnimationName | — | Renge animation token. |
Badge
Compact inline label for status, category, or count. Six semantic variants map directly to the color token system.
Variants
Sizes
<Badge variant="success">Published</Badge>
<Badge variant="warning" size="sm">Beta</Badge>
<Badge variant="danger">Deprecated</Badge>
<Badge variant="info">New</Badge>
<Badge variant="accent" size="lg">Featured</Badge>| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "neutral" | "accent" | "success" | "warning" | "danger" | "info" | "neutral" | Determines background and text color via semantic token pairs (e.g. success-subtle + success). |
| size | "sm" | "md" | "lg" | "md" | Controls padding and font size. |
Chip
Dismissible tag with the same semantic color variants as Badge. Provide onDismiss to render a close button.
Variants
Dismissible (live demo)
{/* Static */}
<Chip variant="accent">Design system</Chip>
{/* Dismissible */}
<Chip variant="neutral" onDismiss={() => removeTag(id)}>
Removable
</Chip>
{/* Dynamic tag list */}
{tags.map(tag => (
<Chip
key={tag.id}
variant="accent"
onDismiss={() => removeTag(tag.id)}
>
{tag.label}
</Chip>
))}| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "neutral" | "accent" | "success" | "warning" | "danger" | "info" | "neutral" | Color semantic — same token mapping as Badge. |
| onDismiss | () => void | — | When provided, renders a × button. Call your own state removal logic here. |
Avatar
User avatar with image or initials fallback. Sizes follow the Fibonacci sequence × 4px.
Sizes — Fibonacci × 4px
Shapes + image
{/* Initials fallback */}
<Avatar initials="RG" size="3" />
{/* Image with initials fallback */}
<Avatar
src="/user.jpg"
alt="Profile photo"
initials="RG"
size="4"
/>
{/* Square shape */}
<Avatar initials="FB" shape="square" size="3" />| Prop | Type | Default | Description |
|---|---|---|---|
| src | string | — | Image URL. Takes priority over initials if provided. |
| alt | string | — | Image alt text and aria-label for the avatar. |
| initials | string | — | Up to 2 characters, displayed when src is absent or fails to load. |
| size | "1" | "2" | "3" | "4" | "5" | "3" | 20 / 32 / 52 / 84 / 136 px — Fibonacci[3–7] × 4. |
| shape | "circle" | "square" | "circle" | circle: full border-radius · square: radius-3 (12px). |
Stat
Key metric display. Trend badges are color-coded via success/danger tokens. Value renders at 3xl — designed to be read at a glance.
Metrics
<Stat value="φ" label="Golden ratio" />
<Stat
value="89"
label="Fibonacci step"
trend="up"
trendValue="+34"
caption="from step 9"
/>
<Stat
value="0ms"
label="Build time delta"
trend="neutral"
trendValue="no change"
/>| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | number | — | Primary metric — rendered at font-size-3xl. |
| label | string | — | Small label above the value. |
| trend | "up" | "down" | "neutral" | — | up=success · down=danger · neutral=fg-muted. Requires trendValue to render the badge. |
| trendValue | string | — | Change amount shown in the trend badge (e.g. '+34', '-2ms'). |
| caption | string | — | Muted caption rendered below the value row. |
Tooltip
Hover tooltip with spring entry animation. Offset uses space-1 (4px = Fibonacci×1) so the gap is always proportioned. Four placements available.
Placements
Rich content
<Tooltip content="This is a tooltip" placement="top">
<Button>Hover me</Button>
</Tooltip>
{/* Rich content */}
<Tooltip
content={
<Stack gap="1">
<Text>PHI = 1.618…</Text>
<Text size="sm" color="fg-subtle">The golden ratio</Text>
</Stack>
}
placement="right"
>
<Badge>φ</Badge>
</Tooltip>
{/* With delay */}
<Tooltip content="Appears after 500ms" delay={500}>
<span>Hover</span>
</Tooltip>| Prop | Type | Default | Description |
|---|---|---|---|
| content | ReactNode | — | Tooltip body — can be a string or any React element. |
| placement | "top" | "bottom" | "left" | "right" | "top" | Where the tooltip appears relative to its trigger. |
| delay | number | 0 | Milliseconds to delay before showing. Useful for reducing tooltip noise on fast mouse movements. |
Skeleton
Animated loading placeholder. Shimmer sweeps at duration-6 (1300ms — Fibonacci). Text variant: the last line is 61.8% wide (1/φ), creating a natural paragraph silhouette.
Variants
Card skeleton pattern
{/* Text — last line at 61.8% (1/φ) */}
<Skeleton variant="text" lines={3} />
{/* Rectangular */}
<Skeleton variant="rectangular" height={80} />
{/* Circular — avatar placeholder */}
<Skeleton variant="circular" width={52} height={52} />
{/* Custom dimensions */}
<Skeleton width="200px" height="120px" />
{/* No animation */}
<Skeleton animated={false} height={40} />| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "text" | "circular" | "rectangular" | "rectangular" | text: renders N lines with last at 61.8% width. circular: full border-radius. |
| lines | number | 3 | Number of lines (variant=text only). |
| width | string | number | "100%" | Width — number treated as px. |
| height | string | number | space-5 | Height — number treated as px. |
| animated | boolean | true | Enable shimmer sweep animation. |
Table
Data table primitives: Table, TableHead, TableBody, TableFoot, TableRow, TableHeader, TableCell. Row padding follows Fibonacci spacing. Header uses semantic uppercase styling.
Full table
| Plant | φ Ratio | Fibonacci | Habitat |
|---|---|---|---|
| Coast Live Oak | 1.618 | 89 | Woodland |
| Blue Wild Rye | 1.382 | 55 | Riparian |
| California Poppy | 1.236 | 34 | Grassland |
| Sword Fern | 1.618 | 89 | Forest |
| Toyon | 1.382 | 55 | Chaparral |
<Table bordered>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Value</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, i) => (
<TableRow key={row.id} index={i}>
<TableCell>{row.name}</TableCell>
<TableCell muted>{row.value}</TableCell>
</TableRow>
))}
</TableBody>
</Table>| Prop | Type | Default | Description |
|---|---|---|---|
| bordered | boolean | false | (Table) Outer border on the container. |
| striped | boolean | false | (Table) Zebra-stripe via data-striped attribute. |
| index | number | — | (TableRow) 0-indexed position — even rows get bg-subtle. |
| muted | boolean | false | (TableCell) Renders in fg-muted for secondary data. |
Accordion
Expandable sections. Height animates with ease-in-out at duration-4 (500ms). The chevron rotates 180° on open. Accordion manages which items are open; AccordionItem is a single row.
Single (default)
Multiple open allowed
<Accordion defaultOpen="phi">
<AccordionItem id="phi" title="What is PHI?">
The golden ratio — 1.618033…
</AccordionItem>
<AccordionItem id="fibonacci" title="Fibonacci sequence">
1, 1, 2, 3, 5, 8, 13…
</AccordionItem>
</Accordion>
{/* Multiple simultaneously open */}
<Accordion multiple>
<AccordionItem id="one" title="Section 1">Content</AccordionItem>
<AccordionItem id="two" title="Section 2">Content</AccordionItem>
</Accordion>| Prop | Type | Default | Description |
|---|---|---|---|
| multiple | boolean | false | (Accordion) Allow multiple items open simultaneously. |
| defaultOpen | string | string[] | — | (Accordion) ID(s) of items open on mount. |
| id | string | — | (AccordionItem) Unique identifier used for open state tracking. |
| title | ReactNode | — | (AccordionItem) Trigger label. |
| disabled | boolean | false | (AccordionItem) Prevents expanding. |
Timeline
Vertical event list. Dots are sized at space-3 (12px = Fibonacci×3). The connector gradient transitions from the item's status color to border-subtle — events flow forward in time.
Build history
- Tokens — v2.2.4Today
Added animation scale. 15 named keyframes. All Fibonacci-derived durations.
- React — v2.3.3Today
44 components. Added data input, display, navigation, feedback, layout, and action categories.
- Tailwind — v2.2.5Today
v4 plugin now injects animation vars and @keyframes blocks.
- @renge-ui/vueTBD
Vue 3 component port. Not started.
<Timeline>
<TimelineItem
title="v1.0 released"
description="First stable token system."
timestamp="Jan 2025"
status="completed"
/>
<TimelineItem
title="React components"
description="21 components added."
timestamp="Feb 2025"
status="active"
/>
<TimelineItem
title="Vue port"
description="Coming soon."
timestamp="TBD"
status="pending"
/>
</Timeline>| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | — | (TimelineItem) Event label. |
| description | ReactNode | — | (TimelineItem) Supporting copy below the title. |
| timestamp | ReactNode | — | (TimelineItem) Time label — aligned right. |
| status | "completed" | "active" | "pending" | "pending" | (TimelineItem) completed=success · active=accent · pending=border. |
| icon | ReactNode | — | (TimelineItem) Optional icon inside the dot (small — use 8–10px icons). |
Alert
Contextual banner with left-border accent and semantic status. Renders with role="alert" for screen reader compatibility.
All statuses
Without title
<Alert status="info" title="Note">
Informational context.
</Alert>
<Alert status="success" title="Deployed">
Production build complete.
</Alert>
<Alert status="danger" title="Error">
Something went wrong.
</Alert>
{/* No title — body only */}
<Alert status="warning">
Approaching rate limit.
</Alert>| Prop | Type | Default | Description |
|---|---|---|---|
| status | "info" | "success" | "warning" | "danger" | "info" | Sets left-border color and background using semantic token pairs. |
| title | string | — | Bold heading inside the alert. Optional — body renders alone if omitted. |
Spinner
Animated loading indicator. The CSS keyframe (rengeSpinnerSpin) is injected once at module load — no runtime overhead per instance.
Sizes
Colors
<Spinner size="md" color="accent" label="Loading tokens" />
<Spinner size="sm" color="fg-subtle" />
{/* In a button */}
<Button variant="outline" disabled>
<Stack direction="horizontal" gap="2" align="center">
<Spinner size="sm" color="accent" />
<span>Saving...</span>
</Stack>
</Button>| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | 16 / 24 / 32 px diameter. |
| color | "accent" | "fg" | "fg-muted" | "accent" | Spinner ring color token. |
| label | string | "Loading" | aria-label for screen readers. |
Progress
Linear progress bar. Value is clamped 0–100. Track and fill colors reference semantic tokens.
PHI-derived values
<Progress value={61.8} color="accent" />
<Progress value={100} color="success" size="lg" />
<Progress value={30} color="danger" radius="none" />
{/* Controlled */}
<Progress
value={uploadProgress}
color="accent"
size="md"
label="Upload progress"
/>| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | — | 0–100 fill percentage. Values outside range are clamped. |
| color | "accent" | "success" | "warning" | "danger" | "accent" | Fill color semantic token. |
| size | "sm" | "md" | "lg" | "md" | Track height: 4 / 8 / 12 px. |
| radius | "none" | "full" | "full" | Track border radius. |
| label | string | — | aria-label for accessibility. |
Toast
Portal-based notification system. Max 5 toasts visible (Fibonacci). Entries use spring easing; exits use ease-in. Wrap your app in ToastProvider and call useToast() anywhere.
Trigger toasts
// 1. Wrap app (or page) in ToastProvider
import { ToastProvider } from "@renge-ui/react";
export default function Layout({ children }) {
return (
<ToastProvider>
{children}
</ToastProvider>
);
}
// 2. Use the hook anywhere inside
import { useToast } from "@renge-ui/react";
function SaveButton() {
const { toast } = useToast();
return (
<Button onClick={() => {
await save();
toast({
title: "Saved",
description: "Your changes are live.",
status: "success",
duration: 3000, // ms — default 5000
});
}}>
Save
</Button>
);
}createPortal to render outside the DOM tree into document.body. Add ToastProvider once at the root — not per-page.| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | — | (toast()) Required — primary message. |
| description | string | — | (toast()) Optional body text below the title. |
| status | "default" | "success" | "warning" | "danger" | "info" | "default" | (toast()) Determines left-border color and icon. |
| duration | number | 5000 | (toast()) Auto-dismiss after N ms. Set to 0 for persistent. |
| id | string | auto | (toast()) Custom ID — use to deduplicate or dismiss programmatically. |
Modal
Portal-based dialog. Overlay fades in; dialog opens with bloom spring easing. Closes on Escape key and overlay click by default. Focus is trapped inside while open.
Sizes
const [open, setOpen] = useState(false);
<Button onClick={() => setOpen(true)}>Open modal</Button>
<Modal open={open} onClose={() => setOpen(false)} size="md">
<ModalHeader>
<Heading level={3}>Title</Heading>
<Button variant="ghost" onClick={() => setOpen(false)}>×</Button>
</ModalHeader>
<ModalBody>
<Text>Modal content goes here.</Text>
</ModalBody>
<ModalFooter>
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="solid" onClick={handleConfirm}>Confirm</Button>
</ModalFooter>
</Modal>| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | — | Controls whether the modal is visible. |
| onClose | () => void | — | Called when the modal should close (Escape key or overlay click). |
| size | "sm" | "md" | "lg" | "xl" | "full" | "md" | Max width — sm:380px · md:520px · lg:720px · xl:960px · full:100vw. |
| closeOnOverlayClick | boolean | true | Whether clicking the backdrop closes the modal. |
| closeOnEsc | boolean | true | Whether Escape key closes the modal. |
Tabs
Tab navigation with spring-eased active underline. Tabs manages open state; Tab registers a value; TabPanel renders conditionally.
Basic tabs
φ = 1.618033… The golden ratio. Two quantities are in the golden ratio if their ratio equals the ratio of their sum to the larger quantity.
<Tabs defaultTab="overview">
<TabList>
<Tab value="overview">Overview</Tab>
<Tab value="props">Props</Tab>
<Tab value="examples">Examples</Tab>
</TabList>
<TabPanel value="overview">
<Text>Overview content</Text>
</TabPanel>
<TabPanel value="props">
<PropsTable>…</PropsTable>
</TabPanel>
<TabPanel value="examples">
<Demo>…</Demo>
</TabPanel>
</Tabs>
{/* Controlled */}
<Tabs value={activeTab} onChange={setActiveTab}>
…
</Tabs>| Prop | Type | Default | Description |
|---|---|---|---|
| defaultTab | string | "" | (Tabs) ID of the tab active on mount. |
| value | string | — | (Tabs) Controlled active tab. |
| onChange | (id: string) => void | — | (Tabs) Called when active tab changes. |
| value | string | — | (Tab / TabPanel) The identifier shared between a Tab and its TabPanel. |
Pagination
Page navigation. Shows at most siblings×2+5 pages — default sibling=1 (Fibonacci). Active page uses accent. Ellipsis replaces gaps greater than 2 pages.
Interactive
Siblings = 2
const [page, setPage] = useState(1);
<Pagination
total={20}
page={page}
onChange={setPage}
/>
{/* More siblings visible */}
<Pagination
total={100}
page={50}
onChange={setPage}
siblings={2}
/>| Prop | Type | Default | Description |
|---|---|---|---|
| total | number | — | Total number of pages. |
| page | number | — | Current page (1-indexed). |
| onChange | (page: number) => void | — | Called with the new page number when the user navigates. |
| siblings | number | 1 | Pages shown on each side of the active page before ellipsis truncation. |
EnergyRing
Circular SVG ring showing an energy or progress level. Stroke width and label scale by PHI with the ring size — self-similar at every size.
Sizes
Colors
Pulse rates
<EnergyRing value={72} />
<EnergyRing value={72} size="xl" color="success" />
<EnergyRing value={50} size="lg" pulse rate="active" />
<EnergyRing value={88} size="xl" label="88%" color="warning" />| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | — | Energy level 0–100. |
| size | "sm" | "md" | "lg" | "xl" | "md" | Ring diameter — sm:32px → xl:136px, each step ≈ φ growth. |
| color | "accent" | "success" | "warning" | "danger" | "accent" | Color channel — maps to semantic token. |
| pulse | boolean | false | Enable breathing animation. |
| rate | "rest" | "active" | "fire" | "active" | Pulse speed — rest=2100ms, active=800ms, fire=500ms. |
| label | string | null | "{value}%" | Center label (shown at lg/xl only). Pass null to hide. |
Pulse
A breathing dot — the minimal alive indicator. Scales to φ at peak. Rate maps to energy states with Fibonacci-derived durations.
Sizes
Colors + rates
With ripple
Inline status indicator
<Pulse />
<Pulse size="lg" color="success" rate="fire" />
<Pulse size="lg" color="accent" ripple />
{/* Status indicator */}
<Stack direction="horizontal" gap="2" align="center">
<Pulse color="success" size="sm" />
<Text size="sm">System online</Text>
</Stack>| Prop | Type | Default | Description |
|---|---|---|---|
| rate | "rest" | "active" | "fire" | "active" | Breath speed — rest=2100ms (~29bpm), active=800ms, fire=500ms. |
| color | "accent" | "success" | "warning" | "danger" | "fg-muted" | "accent" | Dot color channel. |
| size | "sm" | "md" | "lg" | "md" | Dot diameter — 6px / 10px / 16px (fractal scale). |
| ripple | boolean | false | Show expanding ripple ring behind the dot. |
FlowField
A living phyllotaxis dot field. Each dot is placed at a golden-angle position and pulses with a delay derived from its spiral index — energy flows outward along the golden spiral.
Energy levels
Colors
<FlowField />
<FlowField energy="fire" size={300} count={144} />
<FlowField energy="rest" color="fg-subtle" size={200} />
{/* As a decorative background element */}
<div style={{ position: "relative" }}>
<FlowField energy="void" style={{ position: "absolute", inset: 0, opacity: 0.4 }} />
<YourContent />
</div>| Prop | Type | Default | Description |
|---|---|---|---|
| energy | "void" | "rest" | "active" | "fire" | "active" | Controls density, opacity, and pulse rate. void=sparse/slow, fire=dense/fast. |
| count | number | 144 | Maximum dot count at full energy. Actual count scales with energy density. |
| size | number | 400 | Container width and height in px. |
| color | "accent" | "fg-muted" | "fg-subtle" | "accent" | Dot color channel. |
Token API
@renge-ui/tokens exports two ways to consume tokens: createRengeTheme() for generating CSS, and rengeVars for typed CSS variable references.
createRengeTheme(config?)
Generates a complete token set from mathematical first principles. Returns CSS ready to inject and a JS vars map.
import { createRengeTheme } from "@renge-ui/tokens";
const theme = createRengeTheme({
profile: "ocean", // 'ocean' | 'earth' | 'twilight' | 'fire' | 'void' | 'leaf'
mode: "light", // 'light' | 'dark'
baseUnit: 4, // Spacing multiplier in px — try 6 for denser UIs
typeBase: 16, // Root font size in px
scaleRatio: 1.618, // Typography scale ratio (φ = golden ratio)
variance: 0, // 0–1 tolerance drift (0 = exact math, deterministic)
selector: ":root", // CSS selector to wrap the variables in
});
// theme.css — full :root { --renge-* ... } string, ready to inject
// theme.vars — Record<string, string> of every --renge-* variable
// theme.config — resolved config with all defaults appliedrengeVars
A statically typed object of CSS variable references. No runtime dependency — use it anywhere you need a var(--renge-*) string with IDE autocomplete.
import { rengeVars } from "@renge-ui/tokens";
// Color (22 semantic tokens, profile-reactive)
rengeVars.color.bg // "var(--renge-color-bg)"
rengeVars.color.bgSubtle // "var(--renge-color-bg-subtle)"
rengeVars.color.fg // "var(--renge-color-fg)"
rengeVars.color.accent // "var(--renge-color-accent)"
rengeVars.color.danger // "var(--renge-color-danger)"
rengeVars.color.borderFocus // "var(--renge-color-border-focus)"
// Spacing (Fibonacci × baseUnit, steps 0–10)
rengeVars.space[0] // "var(--renge-space-0)" → 0px
rengeVars.space[3] // "var(--renge-space-3)" → 12px (Fib[3] × 4)
rengeVars.space[5] // "var(--renge-space-5)" → 32px
// Typography (PHI scale, 8 steps)
rengeVars.fontSize.base // "var(--renge-font-size-base)"
rengeVars.fontSize.lg // "var(--renge-font-size-lg)"
rengeVars.lineHeight.base // "var(--renge-line-height-base)"
// Motion (Fibonacci × 100ms, steps 0–9)
rengeVars.duration[2] // "var(--renge-duration-2)" → 200ms
rengeVars.duration[5] // "var(--renge-duration-5)" → 800ms
rengeVars.easing.out // "var(--renge-easing-ease-out)"
rengeVars.easing.spring // "var(--renge-easing-spring)"
// Radius (Fibonacci × baseUnit, steps none/1–5/full)
rengeVars.radius[2] // "var(--renge-radius-2)" → 8px
rengeVars.radius.full // "var(--renge-radius-full)" → pillIntegrating with another system
Use rengeVars to map Renge tokens to your own CSS variable names. This is the recommended pattern for adopting Renge alongside an existing design system.
import { createRengeTheme, rengeVars } from "@renge-ui/tokens";
const theme = createRengeTheme({ profile: "earth", mode: "light" });
// Map semantic Renge tokens → your system's variable names
const aliases: [string, string][] = [
// Backgrounds
["--color-bg-primary", rengeVars.color.bg],
["--color-bg-secondary", rengeVars.color.bgSubtle],
["--color-surface", rengeVars.color.bgMuted],
// Foreground
["--color-text", rengeVars.color.fg],
["--color-text-muted", rengeVars.color.fgSubtle],
// Interactive
["--color-primary", rengeVars.color.accent],
["--color-primary-hover",rengeVars.color.accentHover],
// Status
["--color-error", rengeVars.color.danger],
["--color-success", rengeVars.color.success],
["--color-warning", rengeVars.color.warning],
// Spacing bridge
["--space-sm", rengeVars.space[2]],
["--space-md", rengeVars.space[4]],
["--space-lg", rengeVars.space[5]],
];
const aliasCSS = `:root {
${aliases.map(([k, v]) => ` ${k}: ${v};`).join("\n")}
}`;
// Inject Renge base vars first, then your aliases on top
document.head.insertAdjacentHTML("beforeend",
`<style>${theme.css}\n${aliasCSS}</style>`
);All 22 semantic color tokens
| rengeVars key | CSS variable | Role |
|---|---|---|
| color.bg | --renge-color-bg | Page background |
| color.bgSubtle | --renge-color-bg-subtle | Slightly elevated surface |
| color.bgMuted | --renge-color-bg-muted | Muted surface |
| color.bgInverse | --renge-color-bg-inverse | Inverted background |
| color.fg | --renge-color-fg | Primary text |
| color.fgSubtle | --renge-color-fg-subtle | Secondary text |
| color.fgMuted | --renge-color-fg-muted | Placeholder / disabled |
| color.fgInverse | --renge-color-fg-inverse | Text on inverse bg |
| color.border | --renge-color-border | Default divider |
| color.borderSubtle | --renge-color-border-subtle | Hairline divider |
| color.borderFocus | --renge-color-border-focus | Keyboard focus ring |
| color.accent | --renge-color-accent | Primary interactive |
| color.accentHover | --renge-color-accent-hover | Hover state |
| color.accentSubtle | --renge-color-accent-subtle | Tinted background |
| color.success | --renge-color-success | Positive outcome |
| color.successSubtle | --renge-color-success-subtle | Success tint bg |
| color.warning | --renge-color-warning | Caution |
| color.warningSubtle | --renge-color-warning-subtle | Warning tint bg |
| color.danger | --renge-color-danger | Error / destructive |
| color.dangerSubtle | --renge-color-danger-subtle | Danger tint bg |
| color.info | --renge-color-info | Informational |
| color.infoSubtle | --renge-color-info-subtle | Info tint bg |
Animations
15 named animations derived from the token system. Apply via the animation prop on any component that accepts it.
Available tokens
Live examples
breathe
floatpulse{/* On any component that accepts animation prop */}
<Heading level={2} animation="breathe">
Living proportion.
</Heading>
<Badge variant="accent" animation="pulse">
New
</Badge>
<Card animation="bloom">
<Text>Enters with bloom effect</Text>
</Card>
{/* Via CSS variable directly */}
<div style={{ animation: "var(--renge-animation-float)" }}>
Floats gently.
</div>createRengeTheme(). All 15 @keyframes blocks are included in the theme output — no separate import needed. Durations reference Fibonacci-derived --renge-duration-* tokens.Patterns
Common composition patterns using @renge-ui/react components.
Status card
Build status
PassingLast run 2 minutes ago
Form with validation
Metric grid
{/* Every component uses only CSS custom properties */}
{/* Switch profiles and all colors update instantly */}
import { createRengeTheme } from "@renge-ui/tokens";
const theme = createRengeTheme({ profile: "twilight" });
// Inject theme.css and all components adapt — no re-render.var(--renge-*) CSS custom properties. Switch color profiles by injecting a new theme block — no component re-renders required.