PixelButton

Versatile button primitive with tone, size, variant, surface, icon slots, loading state, and a link form for anchors and router links (asChild in React and Vue, a[pxlButton] in Angular).

  • Four variants — solid, soft, outline, ghost — across seven tones
  • Loading state pins the rendered width to prevent collapse when text swaps to spinner
  • Styles a link or router link as the button: asChild in React and Vue, <a pxlButton> in Angular
  • Pixel and linear surfaces inherit from PxlKitSurfaceProvider
  • Accepts every native <button> attribute
Status
stable
Category
actions
Since
v1.0.0

API

import { PixelButton } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
tone'green' | 'cyan' | 'gold' | 'red' | 'purple' | 'pink' | 'neutral''green'Color tone (maps to toneMap).
size'sm' | 'md' | 'lg''md'Visual size.
variant'solid' | 'soft' | 'ghost' | 'outline''solid'Full Variant union — solid | soft | outline | ghost.
surface'pixel' | 'linear'—Surface aesthetic override; defaults to nearest provider.
iconLeftReact.ReactNode—Leading icon node; replaced by a spinner while loading is true.
iconRightReact.ReactNode—Trailing icon node.
loadingboolean—When true, swaps the left icon for a spinner and force-disables the button.
fullWidthboolean—Stretch to fill the parent's inline axis (w-full).
asChildboolean—Render the children as the root element (Radix Slot pattern). Expects a SINGLE React element child; receives merged className, ref, and onClick. Useful for <Link> / <a> wrappers without sacrificing the styling.

Also takes the native attributes and event handlers of <button> (ButtonHTMLAttributes<HTMLButtonElement>).

ref points to <button>.

Accessibility

WCAG target: 2.1 AA

  • button

Renders a native <button> by default so keyboard semantics come for free. When it styles an anchor (asChild, or a[pxlButton] in Angular), the consumer is responsible for ensuring the wrapped element exposes button-equivalent semantics if non-navigational. Loading auto-disables the control and holds it still, with no hover or press feedback; a disabled button also drops its shadow.

Keyboard

KeyActionWhen
EnterActivates the button—
SpaceActivates the button—

Usage

import { PixelButton } from '@pxlkit/ui-kit';

export function Default() {
  return <PixelButton>Click me</PixelButton>;
}

Examples

Default

import { PixelButton } from '@pxlkit/ui-kit';

export function Default() {
  return <PixelButton>Click me</PixelButton>;
}

Tones

import { PixelButton } from '@pxlkit/ui-kit';

export function Tones() {
  return (
    <div className="flex flex-wrap gap-2">
      <PixelButton tone="green">Green</PixelButton>
      <PixelButton tone="cyan">Cyan</PixelButton>
      <PixelButton tone="gold">Gold</PixelButton>
      <PixelButton tone="red">Red</PixelButton>
      <PixelButton tone="purple">Purple</PixelButton>
      <PixelButton tone="pink">Pink</PixelButton>
      <PixelButton tone="neutral">Neutral</PixelButton>
    </div>
  );
}

Sizes

import { PixelButton } from '@pxlkit/ui-kit';

export function Sizes() {
  return (
    <div className="flex flex-wrap items-center gap-2">
      <PixelButton size="sm">Small</PixelButton>
      <PixelButton size="md">Medium</PixelButton>
      <PixelButton size="lg">Large</PixelButton>
    </div>
  );
}

Variants

import { PixelButton } from '@pxlkit/ui-kit';

export function Variants() {
  return (
    <div className="flex flex-wrap gap-2">
      <PixelButton variant="solid">Solid</PixelButton>
      <PixelButton variant="soft">Soft</PixelButton>
      <PixelButton variant="outline">Outline</PixelButton>
      <PixelButton variant="ghost">Ghost</PixelButton>
    </div>
  );
}

Surfaces

import { PixelButton } from '@pxlkit/ui-kit';

export function Surfaces() {
  return (
    <div className="flex flex-wrap gap-2">
      <PixelButton surface="pixel">Pixel</PixelButton>
      <PixelButton surface="linear">Linear</PixelButton>
    </div>
  );
}

With icons

import { PixelButton } from '@pxlkit/ui-kit';

const Arrow = () => (
  <span aria-hidden className="inline-block h-3.5 w-3.5">→</span>
);

export function WithIcons() {
  return (
    <div className="flex flex-wrap gap-2">
      <PixelButton iconLeft={<Arrow />}>Leading</PixelButton>
      <PixelButton iconRight={<Arrow />}>Trailing</PixelButton>
      <PixelButton iconLeft={<Arrow />} iconRight={<Arrow />}>Both</PixelButton>
    </div>
  );
}

Loading

import { PixelButton } from '@pxlkit/ui-kit';

export function Loading() {
  return (
    <div className="flex flex-wrap gap-2">
      <PixelButton loading>Saving</PixelButton>
      <PixelButton loading tone="cyan" variant="outline">Loading</PixelButton>
    </div>
  );
}

Disabled

import { PixelButton } from '@pxlkit/ui-kit';

export function Disabled() {
  return (
    <div className="flex flex-wrap gap-2">
      <PixelButton disabled>Disabled</PixelButton>
      <PixelButton disabled variant="outline">Disabled outline</PixelButton>
    </div>
  );
}

Full width

import { PixelButton } from '@pxlkit/ui-kit';

export function FullWidth() {
  return (
    <div className="w-full max-w-sm">
      <PixelButton fullWidth>Full width</PixelButton>
    </div>
  );
}

As child (link)

import { PixelButton } from '@pxlkit/ui-kit';

export function AsChild() {
  return (
    <PixelButton asChild tone="cyan">
      <a href="https://pxlkit.xyz" target="_blank" rel="noreferrer">
        External link
      </a>
    </PixelButton>
  );
}

Related