Skip to main content
Version: latest
Works everywhere

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.

Live demo →

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

PropTypeDefaultRequiredDescription
titlestringButton 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.
prioritybooleanfalseHydrate the interactive island eagerly; no visual effect.
fullWidthbooleanfalseStretch button to 100% of parent width.
loadingbooleanfalseShow loading spinner and disable interaction.
disabledbooleanfalseDisable all interaction.
iconPropsIconPropsExisting icon to display next to the label.
iconPosition"start" | "end""start"Place the existing icon before or after the label.
redirectLinkPropsRender a geometry-compatible SSR-safe anchor.
aria-labelstringtitleOverride accessible label.
refButtonRefRef to the underlying <button> element.
contentClassNamestringCSS class on the inner content wrapper.
classNamestringCSS class on the root element.
onClickMouseEventHandlerClick handler.
imageImagePropsInline image alternative to iconProps.

ButtonIconProps

Icon-only variant. Requires either aria-label (interactive) or inert (decorative).

PropTypeDefaultRequiredDescription
iconPropsIconPropsIcon to render.
aria-labelstring✅*Accessible label for screen readers.
inertboolean✅*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

TokenDefaultDescription
--pk-button-bgvar(--pk-color-surface-raised)Default surface fallback.
--pk-button-fgvar(--pk-color-fg)Default text/icon fallback.
--pk-button-font-weight600Label font weight.
--pk-button-shadowvar(--pk-shadow-sm)Base fallback shadow.
--pk-button-radiusvar(--pk-radius-pill)Shared radius for all variants.
--pk-button-padding-xvar(--pk-space-xl)Horizontal padding.
--pk-button-padding-y1remVertical padding for contained, outlined, and text.
--pk-button-min-height3.25remShared height; icon-only buttons use it for both axes.

Material and motion

TokenDefaultDescription
--pk-button-hover-lift-2pxHover translation.
--pk-button-press-scale0.985Active-state compression.
--pk-button-motion-duration240msMain transition duration.
--pk-button-motion-easingcubic-bezier(0.22, 1, 0.36, 1)Main transition easing.
--pk-button-sheen-duration560msContained sheen sweep duration.
--pk-button-sheen-opacity0.32Contained sheen strength.
--pk-button-contained-bottom-glowrgb(255 255 255 / 58%)Soft inner bloom in the lower half.
--pk-button-contained-shadow-strength22% (30% for primary)Resting semantic elevation.
--pk-button-contained-hover-shadow-strength30% (38% for primary)Hover semantic elevation.

Semantic token families

Token familyDescription
--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.

app/globals.css
.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, and pointer-events: none. Loading state sets aria-busy="true", keeps the label visible to preserve width, and renders a spinner that inherits currentColor.


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

CriterionNameStatusNote
1.3.1Info and Relationships (A)✅ FulfilledSemantic structure (headings, lists, labels, landmarks) must be conveyed programmatically via HTML or ARIA.
2.1.1Keyboard (A)🔍 Manual reviewAll functionality must be operable via keyboard alone, without requiring specific timing.
2.4.3Focus Order (A)🔍 Manual reviewThe keyboard focus sequence must preserve meaning and operability in the complete page integration context.
2.4.7Focus Visible (AA)🔍 Manual reviewA visible keyboard focus indicator must be present on every interactive element. Verify against the applied product theme.
2.4.11Focus Not Obscured (Min.) (AA)🔍 Manual reviewThe focused component must not be fully hidden by sticky headers, overlays, or other positioned page elements.
2.5.3Label in Name (A)✅ FulfilledFor components with a visible label, the accessible name must contain or match the visible label text.
2.5.8Target Size (Minimum) (AA)🔍 Manual reviewInteractive target areas must be at least 24 × 24 CSS pixels. Verify in the integrated product UI.
4.1.2Name, Role, Value (A)✅ FulfilledName, role, and state of all interactive UI components must be programmatically determinable via native HTML semantics or ARIA.

Test coverage: 5 jest-axe assertions 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:

👉 Open Button in Storybook


Source

TypeScript types: src/components/button/Button.model.ts