Skip to content

Button

In the app

Five variants, five sizes, one component. The outline is the default because it is what the app reaches for; navy is for the one primary action on a screen.

@varick/ui/components/buttonAdded 26 Aug 2026

Usage

import { Button } from "@varick/ui/components/button";

export function Example() {
  return (
    <>
      <Button variant="primary" onClick={run}>Run agent</Button>
      <Button>Assign</Button>
    </>
  );
}

Variants

Sizes

<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
<Button size="icon" aria-label="More"><DotsIcon /></Button>
<Button size="icon-sm" aria-label="More"><DotsIcon /></Button>

With an icon

<Button variant="primary"><PlusIcon /> New agent</Button>
<Button>Export <ExternalIcon /></Button>

Loading and disabled

<Button variant="primary" loading={isRunning} onClick={run}>
  {isRunning ? "Running" : "Run agent"}
</Button>
<Button disabled>Disabled</Button>

API reference

Every component also accepts className and the native props of its root element.

PropTypeDefaultDescription
variant'outline' | 'primary' | 'ghost' | 'destructive' | 'link''outline'Emphasis. One primary per view.
size'sm' | 'md' | 'lg' | 'icon' | 'icon-sm''md'Height 28, 32, 36; icon squares 30 and 24.
loadingbooleanfalseShows a spinner, sets aria-busy and disables the button.
disabledbooleanfalseDims to 50% and drops pointer events.
...propsComponentProps<'button'>Every native button prop passes through. type defaults to "button".

Accessibility

  • Icon-only buttons take an aria-label; the icon alone is never the name.
  • Focus is the global 2px accent outline, offset 2px, on :focus-visible only.
  • A loading button stays in the tab order and announces aria-busy rather than disappearing.

Styles

The CSS as it appears in the app. The live component above is the same values as Tailwind classes.

.btn-primary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--canvas);
  color: #FFFFFF;
  font-size: 13px;
  font-weight: 500;
  border-radius: 4px;
  padding: 8px 14px;
  transition: opacity 140ms var(--ease-out), transform 140ms var(--ease-out);
}
.btn-primary:hover { opacity: 0.92; }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }

.btn-ghost {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--hairline);
  border-radius: 4px;
  padding: 7px 12px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--fg-high);
  background: #FFFFFF;
}
.btn-ghost:hover { border-color: var(--hairline-hover); background: var(--fill-soft); }
.btn-primary:active, .btn-ghost:active { transform: scale(0.97); }