InfinitiBit Design System

Button

A clickable action trigger with token-driven variants and sizes.

Button is the primary action trigger of the design system. It is styled entirely through design tokens, so switching themes restyles it with no markup changes.

Installation

pnpm add @infinitibit_gmbh/ui @infinitibit_gmbh/theme-default

Usage

import '@infinitibit_gmbh/theme-default/theme.css';
import '@infinitibit_gmbh/ui/styles.css';
import { Button } from '@infinitibit_gmbh/ui';

<Button variant="primary" size="md">
	Save
</Button>;

Variants

Three visual styles: primary (filled), secondary (outlined), and tertiary (text-only).

<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="tertiary">Tertiary</Button>

Sizes

Four sizes: xs, sm, md (default), and lg.

<Button size="xs">Extra small</Button>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

Destructive

Add destructive to any variant to signal a dangerous action.

<Button variant="primary" destructive>
	Delete
</Button>

Icons

Add a leading or trailing icon with leftIcon / rightIcon — always via the Icon API, never a raw SVG.

import { Button } from '@infinitibit_gmbh/ui';
import { Icon } from '@infinitibit_gmbh/ui/icons';

<Button leftIcon={<Icon name="search" />}>Search</Button>;

Composition (asChild)

Set asChild to render the Button's styling and semantics onto your own child element instead of a <button> — the standard pattern for a Button-as-link. The button classes, props, and ref are merged onto the child, and the icon slots compose inside it, so the link looks and reads exactly like a button.

import { Button } from '@infinitibit_gmbh/ui';
import { Icon } from '@infinitibit_gmbh/ui/icons';

// Renders <a class="ib-btn …" href="/docs"><icon/>Read the docs</a> — no <button>.
<Button asChild leftIcon={<Icon name="search" />}>
	<a href="/docs">Read the docs</a>
</Button>;

Pass exactly one child element (Radix Slot merges onto a single child). Choose a child with the right native semantics — <a href> for navigation, <button> for actions — so assistive tech announces the correct role.

API

PropTypeDefaultDescription
asChildbooleanRender as the single child element instead of a <button>, merging the button styling/semantics + props/ref onto it (Radix Slot). Use for a Button-as-link (<a>) or any custom interactive element.
destructivebooleanfalseSwaps the active variant to the critical/danger ramp.
leftIconReactNodeIcon before the label — use the Icon API, e.g. <Icon name="search" />.
rightIconReactNodeIcon after the label — use the Icon API.
size"xs" | "sm" | "md" | "lg"mdButton size.
variant"primary" | "secondary" | "tertiary"primaryVisual style: filled, outlined, or text-only.

Plus the className escape hatch (appended classes) and all native <button> attributes (disabled, onClick, type, …).

This table is generated from the component's TypeScript source by the stable catalog (issue #51) and kept in sync by the verify:catalog gate — no hand maintenance.

Server & client

Button is universal — it has no client-only hooks or state, so it renders in React Server Components (zero client JS for a static button) and in client components alike. Add interactivity (onClick) from your own client component; the icon slots accept any node.

Accessibility

  • Renders a native <button> — focusable, announced by assistive tech, and operable via Enter and Space.
  • :focus-visible shows a keyboard-only focus ring (hidden for mouse clicks).
  • disabled uses the native attribute — correct semantics and removed from the tab order.
  • Verified by the automated axe + keyboard gates plus a Storybook interaction test.

On this page