Tuimorphic

Button

Terminal-aesthetic button component built on Base UI for accessibility.

Import

import { Button } from 'tuimorphic';

Basic Usage

<Button>Click me</Button>

Props

PropTypeDefaultDescription
variant'primary' | 'secondary''primary'Visual style variant of the button
disabledbooleanfalseWhether the button is disabled and non-interactive
children*React.ReactNodeButton content (text, icons, or elements)
classNamestringAdditional CSS class names for custom styling
onClick(event: MouseEvent) => voidClick event handler
type'button' | 'submit' | 'reset''button'HTML button type attribute
refReact.Ref<HTMLButtonElement>Forwarded ref to the underlying button element

All standard HTML button attributes are also supported (onFocus, onBlur, aria-*, etc.).

Variants

Primary & Secondary

<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>

Primary buttons are for main actions. They have a solid background with inverted text.

Secondary buttons are for auxiliary actions. They have a transparent background with an inset border.

States

Primary States

<Button>Default</Button>
<Button disabled>Disabled</Button>

Secondary States

<Button variant="secondary">Default</Button>
<Button variant="secondary" disabled>Disabled</Button>

With Icons

Use terminal-style bracket notation for icons:

<Button>[+] Add Item</Button>
<Button>[x] Delete</Button>
<Button variant="secondary">[<] Back</Button>
<Button variant="secondary">[>] Next</Button>

Custom Sizes

Apply custom font sizes and padding via style or className:

<Button style={{ fontSize: '10px', padding: '4px 8px' }}>XS</Button>
<Button style={{ fontSize: '12px', padding: '6px 12px' }}>SM</Button>
<Button>Default</Button>
<Button style={{ fontSize: '16px', padding: '12px 24px' }}>LG</Button>

Interactive Example

0
const [count, setCount] = useState(0);
 
<Button onClick={() => setCount(c => c - 1)}>[-]</Button>
<span>{count}</span>
<Button onClick={() => setCount(c => c + 1)}>[+]</Button>
<Button variant="secondary" onClick={() => setCount(0)}>Reset</Button>

Full Width

<Button style={{ width: '100%' }}>Full Width Primary</Button>
<Button variant="secondary" style={{ width: '100%' }}>Full Width Secondary</Button>

Button Group

Create toggle groups by managing selection state:

const [selected, setSelected] = useState('A');
 
<div className="flex">
  {['A', 'B', 'C'].map((option) => (
    <Button
      key={option}
      variant={selected === option ? 'primary' : 'secondary'}
      onClick={() => setSelected(option)}
      style={{ marginLeft: option !== 'A' ? '-1px' : 0 }}
    >
      Option {option}
    </Button>
  ))}
</div>

Loading State

Implement loading with disabled state and text change:

const [loading, setLoading] = useState(false);
 
const handleClick = () => {
  setLoading(true);
  setTimeout(() => setLoading(false), 2000);
};
 
<Button onClick={handleClick} disabled={loading}>
  {loading ? '[...] Loading' : 'Submit'}
</Button>

Accessibility

  • Built on Base UI's accessible button primitive
  • Supports keyboard navigation (Enter/Space to activate)
  • disabled attribute properly disables pointer events and keyboard interaction
  • Focus states are clearly visible with outline
  • Works with screen readers

Styling

The Button uses CSS custom properties for theming:

VariableDescription
--theme-buttonPrimary button background
--theme-button-textPrimary button text color
--theme-button-backgroundSecondary button background
--theme-button-foregroundSecondary button border color
--theme-focused-borderFocus outline color

Base UI

This component wraps the Base UI Button primitive. See the Base UI Button documentation for additional details.

On this page