Button works in both server and client environments. Always use the standard import (e.g. @prokodo/ui/button) — the library detects the runtime automatically.
Button
The Button component is the core interactive element in @prokodo/ui. It supports three equally sized visual variants, seven semantic colors, icons before or after the label, icon-only mode, loading states, full-width layout, and SSR-safe redirect links.
Overview
import { Button } from "@prokodo/ui/button"
;<Button
title="Click Me"
color="primary"
variant="contained"
onClick={handleClick}
/>
Import
import { Button } from "@prokodo/ui/button"
CSS (import once at app root):
import "@prokodo/ui/button.css"
Props
ButtonProps is a discriminated union of ButtonDefaultProps (labeled button) and ButtonIconProps (icon-only button).
ButtonDefaultProps
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
title | string | — | ✅ | Button label text (also used as fallback aria-label). |
variant | "contained" | "outlined" | "text" | "contained" | — | Visual style variant. |
color | "primary" | "secondary" | "success" | "error" | "info" | "warning" | "inherit" | "primary" | — | Semantic color token. |
priority | boolean | false | — | Hydrate the interactive island eagerly; no visual effect. |
fullWidth | boolean | false | — | Stretch button to 100% of parent width. |
loading | boolean | false | — | Show loading spinner and disable interaction. |
disabled | boolean | false | — | Disable all interaction. |
iconProps | IconProps | — | — | Existing icon to display next to the label. |
iconPosition | "start" | "end" | "start" | — | Place the existing icon before or after the label. |
redirect | LinkProps | — | — | Render a geometry-compatible SSR-safe anchor. |
aria-label | string | title | — | Override accessible label. |
ref | ButtonRef | — | — | Ref to the underlying <button> element. |
contentClassName | string | — | — | CSS class on the inner content wrapper. |
className | string | — | — | CSS class on the root element. |
onClick | MouseEventHandler | — | — | Click handler. |
image | ImageProps | — | — | Inline image alternative to iconProps. |
ButtonIconProps
Icon-only variant. Requires either aria-label (interactive) or inert (decorative).
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
iconProps | IconProps | — | ✅ | Icon to render. |
aria-label | string | — | ✅* | Accessible label for screen readers. |
inert | boolean | — | ✅* | Mark as decorative (no a11y label needed). |
* Either aria-label or inert must be provided (not both).
Design Tokens
Customise Button via CSS custom properties on :root or a scoped ancestor.
Layout and typography
| Token | Default | Description |
|---|---|---|
--pk-button-bg | var(--pk-color-surface-raised) | Default surface fallback. |
--pk-button-fg | var(--pk-color-fg) | Default text/icon fallback. |
--pk-button-font-weight | 600 | Label font weight. |
--pk-button-shadow | var(--pk-shadow-sm) | Base fallback shadow. |
--pk-button-radius | var(--pk-radius-pill) | Shared radius for all variants. |
--pk-button-padding-x | var(--pk-space-xl) | Horizontal padding. |
--pk-button-padding-y | 1rem | Vertical padding for contained, outlined, and text. |
--pk-button-min-height | 3.25rem | Shared height; icon-only buttons use it for both axes. |
Material and motion
| Token | Default | Description |
|---|---|---|
--pk-button-hover-lift | -2px | Hover translation. |
--pk-button-press-scale | 0.985 | Active-state compression. |
--pk-button-motion-duration | 240ms | Main transition duration. |
--pk-button-motion-easing | cubic-bezier(0.22, 1, 0.36, 1) | Main transition easing. |
--pk-button-sheen-duration | 560ms | Contained sheen sweep duration. |
--pk-button-sheen-opacity | 0.32 | Contained sheen strength. |
--pk-button-contained-bottom-glow | rgb(255 255 255 / 58%) | Soft inner bloom in the lower half. |
--pk-button-contained-shadow-strength | 22% (30% for primary) | Resting semantic elevation. |
--pk-button-contained-hover-shadow-strength | 30% (38% for primary) | Hover semantic elevation. |
Semantic token families
| Token family | Description |
|---|---|
--pk-button-contained-background-{color} | Contained surface overrides. |
--pk-button-contained-foreground-{color} | Solid icon and fallback foreground. |
--pk-button-contained-ink-{color} | Contained label gradient. |
--pk-button-content-{color} | Outlined/text label and icon gradient. |
--pk-button-content-fallback-{color} | Solid fallback for gradient text. |
{color} is inherit, primary, secondary, success, info, warning, or error. Light and dark themes swap the relevant values automatically. Dark contained buttons use a softer charcoal foreground rather than pure black.
.checkoutCta {
--pk-button-contained-background-primary: linear-gradient(
112deg,
#4aa8ff,
#43ecff
);
--pk-button-contained-bottom-glow: rgb(255 255 255 / 64%);
}
Disabled state uses
opacity: 0.5, reduced saturation, andpointer-events: none. Loading state setsaria-busy="true", keeps the label visible to preserve width, and renders a spinner that inheritscurrentColor.
Variants and icons
<Button title="Primary action" color="primary" variant="contained" />
<Button title="Secondary action" color="primary" variant="outlined" />
<Button title="Quiet action" color="primary" variant="text" />
<Button
title="Continue"
iconProps={{ name: "ArrowRight01Icon" }}
iconPosition="end"
/>
<Button
aria-label="Continue"
iconProps={{ name: "ArrowRight01Icon" }}
/>
Contained, outlined, and text variants share the same minimum height. Icon-only buttons use the same value as a square touch target.
Redirect behavior
When redirect is set, the root becomes a BaseLink anchor while retaining the same classes, content wrapper, dimensions, theme, and interaction states as a native button.
<Button
title="Open report"
iconProps={{ name: "ArrowRight01Icon" }}
iconPosition="end"
redirect={{
href: "/report",
target: "_self",
}}
/>
AIC Note
Use the standard import path in application code:
import { Button } from "@prokodo/ui/button"
No separate /client or /lazy import selection is required in consumer code.
priority makes the button island hydrate eagerly instead of waiting for visibility. It does not alter color, elevation, size, or any other visual style.
WCAG 2.2 Status
| Criterion | Name | Status | Note |
|---|---|---|---|
| 1.3.1 | Info and Relationships (A) | ✅ Fulfilled | Semantic structure (headings, lists, labels, landmarks) must be conveyed programmatically via HTML or ARIA. |
| 2.1.1 | Keyboard (A) | 🔍 Manual review | All functionality must be operable via keyboard alone, without requiring specific timing. |
| 2.4.3 | Focus Order (A) | 🔍 Manual review | The keyboard focus sequence must preserve meaning and operability in the complete page integration context. |
| 2.4.7 | Focus Visible (AA) | 🔍 Manual review | A visible keyboard focus indicator must be present on every interactive element. Verify against the applied product theme. |
| 2.4.11 | Focus Not Obscured (Min.) (AA) | 🔍 Manual review | The focused component must not be fully hidden by sticky headers, overlays, or other positioned page elements. |
| 2.5.3 | Label in Name (A) | ✅ Fulfilled | For components with a visible label, the accessible name must contain or match the visible label text. |
| 2.5.8 | Target Size (Minimum) (AA) | 🔍 Manual review | Interactive target areas must be at least 24 × 24 CSS pixels. Verify in the integrated product UI. |
| 4.1.2 | Name, Role, Value (A) | ✅ Fulfilled | Name, role, and state of all interactive UI components must be programmatically determinable via native HTML semantics or ARIA. |
Test coverage: 5
jest-axeassertions in the Button test suite, plus redirect and icon-only accessible-name coverage. Criteria marked 🔍 require manual verification in the final product integration and theme context.
Storybook
Explore all Button variants, controls, and a11y annotations live:
Source
TypeScript types: src/components/button/Button.model.ts