Skip to content

Button

Triggers an action, such as saving a form or opening a dialog. For navigation, use Link, or ButtonLink for a link that looks like a button.

Code
demo.tsx
import {Button} from '@earendil-works/elanor-react';

export default function ButtonDemo() {
  return <Button>Save changes</Button>;
}

Usage

import {Button} from '@earendil-works/elanor-react';

<Button onClick={save}>Save changes</Button>;

Button renders type="button" by default. Set type="submit" to submit a form.

Hierarchy

Use one primary button per view for the main action. Use secondary and tertiary for supporting actions.

Code
hierarchy.tsx
import {Button, Stack} from '@earendil-works/elanor-react';

export default function ButtonHierarchy() {
  return (
    <Stack direction="row" gap="small" wrap="wrap">
      <Button>Primary</Button>
      <Button hierarchy="secondary">Secondary</Button>
      <Button hierarchy="tertiary">Tertiary</Button>
    </Stack>
  );
}

Semantic

negative marks destructive actions; positive marks confirming ones. Combine with any hierarchy.

Code
semantic.tsx
import {Button, Stack} from '@earendil-works/elanor-react';

export default function ButtonSemantic() {
  return (
    <Stack direction="row" gap="small" wrap="wrap">
      <Button semantic="negative">Delete project</Button>
      <Button semantic="positive">Approve request</Button>
    </Stack>
  );
}

Density

Use compact in dense layouts, such as toolbars and table rows. The touch target keeps the default height.

Code
density.tsx
import {Button, Stack} from '@earendil-works/elanor-react';

export default function ButtonDensity() {
  return (
    <Stack direction="row" gap="small" align="center" wrap="wrap">
      <Button>Default</Button>
      <Button density="compact">Compact</Button>
    </Stack>
  );
}

Icons

iconStart and iconEnd add decorative icons; the label names the button. For an icon without a label, use Icon button.

Code
icons.tsx
import {AddIcon, ArrowForwardIcon} from '@earendil-works/elanor-icons-react';
import {Button, Stack} from '@earendil-works/elanor-react';

export default function ButtonIcons() {
  return (
    <Stack direction="row" gap="small" wrap="wrap">
      <Button iconStart={<AddIcon />}>New project</Button>
      <Button hierarchy="secondary" iconEnd={<ArrowForwardIcon />}>
        Continue
      </Button>
    </Stack>
  );
}

Pending

pending disables the button while an action runs, keeps it focusable, and sets aria-busy.

Code
pending.tsx
import {Button} from '@earendil-works/elanor-react';
import {useState} from 'react';

export default function ButtonPending() {
  const [pending, setPending] = useState(false);

  return (
    <Button
      pending={pending}
      onClick={() => {
        setPending(true);
        setTimeout(() => setPending(false), 2000);
      }}
    >
      {pending ? 'Saving…' : 'Save changes'}
    </Button>
  );
}

ButtonLink renders a plain a that looks like a Button, for navigation such as a call to action. It takes the same hierarchy, semantic, density, iconStart, and iconEnd, and keeps link semantics: screen readers announce it as a link, and Enter follows it. Don’t pass a link to Button’s render; that gives the a a button role.

Code
link.tsx
import {ArrowForwardIcon} from '@earendil-works/elanor-icons-react';
import {ButtonLink, Stack} from '@earendil-works/elanor-react';

export default function ButtonLinkDemo() {
  return (
    <Stack direction="row" gap="small" wrap="wrap">
      <ButtonLink href="#" iconEnd={<ArrowForwardIcon />}>
        Open dashboard
      </ButtonLink>
      <ButtonLink href="#" hierarchy="secondary">
        Read the guide
      </ButtonLink>
    </Stack>
  );
}

ButtonLink passes its props and ref through to the a, so a router can wrap it, as with TanStack Router’s createLink.

A link can’t be disabled or pending. When a destination isn’t available, render a Button or text instead.

Disabled

Code
disabled.tsx
import {Button} from '@earendil-works/elanor-react';

export default function ButtonDisabled() {
  return <Button disabled>Save changes</Button>;
}

API

Button

PropTypeDefaultDescription
hierarchy'primary' | 'secondary' | 'tertiary''primary'Visual emphasis. Use one primary button per view.
semantic'neutral' | 'negative' | 'positive''neutral'negative for destructive actions, positive for confirming ones.
density'compact' | 'default''default'compact removes block padding but keeps the touch target height.
iconStartReactNode-Decorative icon before the label.
iconEndReactNode-Decorative icon after the label.
pendingbooleanfalseDisables the button while an action runs, keeps it focusable, and sets aria-busy.
type'submit' | 'reset' | 'button''button'

Also accepts Base UI Button props.

A link that looks like a Button, for navigation such as a call to action. Renders a plain a without a button role, and passes its props and ref through, so a router can wrap it.

PropTypeDefaultDescription
hierarchy'primary' | 'secondary' | 'tertiary''primary'Visual emphasis. Use one primary button per view.
semantic'neutral' | 'negative' | 'positive''neutral'negative for destructive actions, positive for confirming ones.
density'compact' | 'default''default'compact removes block padding but keeps the touch target height.
iconStartReactNode-Decorative icon before the label.
iconEndReactNode-Decorative icon after the label.

Renders <a>. Also accepts its HTML attributes and ref.