Skip to content
Highlight

ComponentsUpdated Sep 9, 2026

Button

The action primitive. Variants carry intent, sizes carry density, and the same styling powers triggers in Dialog, Menu, and Sheet, so a button looks the same wherever it appears.

Import

Source: packages/ui/src/components/button

tsx
import { Button, ButtonLink, buttonVariants } from '@highlight/ui'

Variants

default for most actions, primary and bold for the one action that matters, secondary for outlined, tertiary for quiet. success, danger, and meeting are for status-bearing actions only.

tsx
import { Button } from '@highlight/ui'

export default function ButtonVariants() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button variant="default">Default</Button>
      <Button variant="primary">Primary</Button>
      <Button variant="bold">Bold</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="tertiary">Tertiary</Button>
      <Button variant="success">Success</Button>
      <Button variant="danger">Danger</Button>
      <Button variant="meeting">Meeting</Button>
    </div>
  )
}

Sizes

medium is the default. Icons scale with the size automatically.

tsx
import { Button } from '@highlight/ui'

export default function ButtonSizes() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button size="tiny">Tiny</Button>
      <Button size="small">Small</Button>
      <Button size="medium">Medium</Button>
      <Button size="large">Large</Button>
    </div>
  )
}

Shapes

square and circle make icon-only buttons; always add an aria-label. rounded keeps the label with pill corners.

tsx
import { ArrowUp } from 'lucide-react'
import { Button } from '@highlight/ui'

export default function ButtonShapes() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button shape="rounded">Rounded</Button>
      <Button aria-label="Send" shape="square">
        <ArrowUp />
      </Button>
      <Button aria-label="Send" shape="circle">
        <ArrowUp />
      </Button>
      <Button aria-label="Send" shape="circle" size="small" variant="primary">
        <ArrowUp />
      </Button>
    </div>
  )
}

Left and right slots

left and right accept any node; lucide icons are sized for you.

tsx
import { ArrowLeft, ArrowRight } from 'lucide-react'
import { Button } from '@highlight/ui'

export default function ButtonIcons() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button left={<ArrowLeft />}>Back</Button>
      <Button right={<ArrowRight />}>Next</Button>
      <Button variant="primary" left={<ArrowLeft />} right={<ArrowRight />}>
        Both
      </Button>
    </div>
  )
}

Hotkey chips

hotkey renders keycap chips from a "cmd+k"-style string. Aliases like cmd, shift, enter, esc map to symbols.

tsx
import { Button } from '@highlight/ui'

export default function ButtonHotkey() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button hotkey="cmd+enter">Send</Button>
      <Button variant="primary" hotkey="cmd+k">
        Search
      </Button>
      <Button variant="secondary" size="small" hotkey="esc">
        Dismiss
      </Button>
    </div>
  )
}

Loading and disabled

loading swaps in a spinner and blocks clicks but keeps full opacity, because the press already landed. disabled fades.

tsx
import { useState } from 'react'
import { ArrowUp } from 'lucide-react'
import { Button } from '@highlight/ui'

export default function ButtonStates() {
  const [loading, setLoading] = useState(false)

  const submit = () => {
    setLoading(true)
    setTimeout(() => setLoading(false), 1500)
  }

  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button variant="primary" loading={loading} onClick={submit}>
        {loading ? 'Saving' : 'Save'}
      </Button>
      <Button loading>Loading</Button>
      <Button loading aria-label="Send" shape="circle">
        <ArrowUp />
      </Button>
      <Button disabled>Disabled</Button>
      <Button disabled variant="primary">
        Disabled
      </Button>
    </div>
  )
}

Props

Props added on top of the underlying element or Base UI part.

PropTypeDefaultDescription
variant'default' | 'primary' | 'bold' | 'secondary' | 'tertiary' | 'success' | 'danger' | 'meeting''default'Intent. Inherits from a surrounding ButtonGroup when omitted.
size'tiny' | 'small' | 'medium' | 'large''medium'Height, padding, and icon size.
shape'rounded' | 'square' | 'circle'square and circle are icon-only.
left / rightReactNodeLeading and trailing slots.
hotkeystringKeycap chips, e.g. "cmd+enter".
loadingbooleanfalseShows a spinner in place of the leading slot (or the content for icon buttons) and disables the button.
  • Renders a native <button>; all button attributes pass through, and a ref reaches the element.
  • buttonVariants is the cva used for the styling, exported so triggers elsewhere can share it.