Button System: A Complete Guide to Variants and Dark Mode

Our button system uses semantic CSS variables with CSS Modules for consistent styling across themes. Each button variant maps to specific design tokens that handle colors, hover states, and dark mode automatically.

Design Tokens

Our button system is built on semantic tokens:

/* Primary Button */
--btn-primary-bg: var(--accent-blue)
--btn-primary-text: white
--btn-primary-hover: var(--blue500)
 
/* Secondary Button */
--btn-secondary-bg: var(--mono2)
--btn-secondary-text: var(--mono12)
--btn-secondary-hover: var(--mono3)
--btn-secondary-border: var(--mono5)
 
/* Ghost Button */
--btn-ghost-hover: var(--mono2)
--btn-ghost-text: var(--mono11)
--btn-ghost-text-hover: var(--mono12)
 
/* Common Button Properties */
--btn-disabled-opacity: 0.5
--btn-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)

CSS Module Approach

Our Button component uses CSS Modules for scoped styling:

/* Button.module.css */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius);
  font-weight: 500;
  transition: var(--focus-transition);
  cursor: pointer;
  position: relative;
  white-space: nowrap;
}
 
.btn--primary {
  background-color: var(--btn-primary-bg);
  color: var(--btn-primary-text);
  box-shadow: var(--btn-shadow);
  border: none;
}
 
/* Additional variant styles... */

The component then imports and uses these styles:

import styles from './Button.module.css';
 
<button
  className={cn(
    styles.btn,
    styles[`btn--${variant}`],
    styles[`btn--${size}`],
    className
  )}
>
  {children}
</button>

Primary Variant

The primary call-to-action button. Uses --btn-primary-* tokens.

<Button size="sm">Small</Button>
<Button>Default</Button>
<Button size="lg">Large</Button>
<Button isLoading loadingText="Loading">Loading</Button>
<Button disabled>Disabled</Button>

Outline Variant

Secondary actions using --btn-secondary-* tokens.

<Button variant="outline">Secondary Action</Button>

Ghost Variant

Subtle actions using --btn-ghost-* tokens.

<Button variant="ghost">Subtle Action</Button>

Common Use Cases

Form Actions

Primary actions use primary variant, secondary actions use ghost:

<div className="btn-row">
  <Button>Save changes</Button>
  <Button variant="ghost">Discard</Button>
</div>

Dialog Actions

Destructive actions use outline variant for visual distinction:

<div className="btn-row">
  <Button variant="outline">Delete Account</Button>
  <Button variant="ghost">Cancel</Button>
</div>

Toolbar Groups

Ghost buttons with consistent borders:

<div className="toolbar-container">
  <Button variant="ghost" position="left">Copy</Button>
  <Button variant="ghost" position="middle">Paste</Button>
  <Button variant="ghost" position="right">Delete</Button>
</div>

Multi-Step Forms

Combine outline and primary variants for navigation:

<div className="btn-row btn-row--spaced">
  <Button variant="outline">Previous</Button>
  <Button>Next Step</Button>
</div>

Loading States

Loading states maintain variant styling:

<div className="btn-row">
  <Button isLoading loadingText="Saving...">Save Draft</Button>
  <Button variant="outline" isLoading loadingText="Publishing...">
    Publish
  </Button>
</div>

CSS Module Features

The Button module includes specialized CSS classes:

/* Button modifiers for toolbar positioning */
.btn--left {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
 
.btn--middle {
  border-radius: 0;
  border-left: 1px solid var(--mono4);
  border-right: 1px solid var(--mono4);
}
 
.btn--right {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
 
/* Loading animation */
.btn__loader {
  margin-right: 0.5rem;
  height: 1rem;
  width: 1rem;
  animation: spin 1s linear infinite;
}
 
@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

Usage Guidelines

Choose variants based on action importance:

  • Primary (--btn-primary-*): Primary actions, main calls-to-action

    • Form submissions
    • Confirmation dialogs
    • Next steps
    • Save/Submit actions
  • Outline (--btn-secondary-*): Secondary actions

    • Alternative options
    • Previous/Back buttons
    • Cancel options
    • Destructive actions
  • Ghost (--btn-ghost-*): Subtle actions

    • Toolbar buttons
    • Button groups
    • Menu items
    • Less prominent options

Accessibility Features

Our button system includes:

  • High contrast using semantic color tokens
  • Focus rings via global accessibility styles (not component-specific)
  • Loading states with visual indicators
  • Disabled states using --btn-disabled-opacity
  • Consistent touch targets
  • Keyboard navigation support

Try toggling dark mode to see how the semantic tokens automatically adjust!

Correspondence

Get notified when I write. Feel free to reply directly.