Default

Transparent at rest. Background appears on hover and when pressed.

← unpressed vs pressed
<button class="toggle" aria-pressed="false"
        aria-label="Bookmark">
  <svg aria-hidden="true" width="16" height="16"
       viewBox="0 0 24 24" fill="none" stroke="currentColor"
       stroke-width="2" stroke-linecap="round"
       stroke-linejoin="round">
    <path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"/>
  </svg>
</button>

Outline

Visible border and subtle shadow at rest.

<button class="toggle" data-variant="outline"
        aria-pressed="true" aria-label="Toggle italic">
  <svg aria-hidden="true" width="16" height="16" ...>...</svg>
</button>

<button class="toggle" data-variant="outline"
        aria-pressed="false" aria-label="Toggle bold">
  <svg aria-hidden="true" width="16" height="16" ...>...</svg>
</button>

With Text

Icon and label together.

<button class="toggle" aria-pressed="false">
  <svg aria-hidden="true" width="16" height="16" ...>...</svg>
  Italic
</button>

Size

Small, default, and large via data-size.

<button class="toggle" data-variant="outline" data-size="sm"
        aria-pressed="false">Small</button>

<button class="toggle" data-variant="outline"
        aria-pressed="false">Default</button>

<button class="toggle" data-variant="outline" data-size="lg"
        aria-pressed="false">Large</button>

Disabled

Non-interactive at 50% opacity.

<button class="toggle" data-variant="outline"
        aria-pressed="false" disabled>Disabled</button>

<button class="toggle" data-variant="outline"
        aria-pressed="true" disabled>Disabled</button>

CSS view file

/* -- Toggle component ------------------------------------------ */

@layer components {
  .toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    font-weight: 500;
    font-family: var(--font-sans);
    line-height: 1;
    white-space: nowrap;
    border-radius: var(--radius-md);
    border: 1px solid transparent;
    background: transparent;
    color: var(--muted-foreground);
    cursor: pointer;
    transition: color 150ms ease, background-color 150ms ease, box-shadow 150ms ease;
    outline: none;
    flex-shrink: 0;
    height: 2.25rem;
    padding: 0 0.5rem;
    min-width: 2.25rem;

    &:hover {
      background-color: var(--muted);
      color: var(--muted-foreground);
    }

    &[aria-pressed="true"] {
      background-color: var(--accent);
      color: var(--accent-foreground);

      &:hover {
        background-color: color-mix(in oklch, var(--accent) 85%, var(--foreground));
      }
    }

    &:focus-visible {
      outline: 2px solid var(--ring);
      outline-offset: 2px;
    }

    &:disabled {
      pointer-events: none;
      opacity: 0.5;
    }

    & svg {
      pointer-events: none;
      flex-shrink: 0;

      &:not([class*="size-"]) { width: 1rem; height: 1rem; }
    }

    /* -- Variants -------------------------------------------- */
    &[data-variant="outline"] {
      border: 1px solid var(--input);
      background: transparent;
      box-shadow: var(--shadow-xs);

      &:hover {
        background-color: var(--accent);
        color: var(--accent-foreground);
      }

      &[aria-pressed="true"] {
        background-color: var(--accent);
        color: var(--accent-foreground);

        &:hover {
          background-color: color-mix(in oklch, var(--accent) 85%, var(--foreground));
        }
      }
    }

    /* -- Sizes ----------------------------------------------- */
    &[data-size="sm"]  { height: 2rem;  padding: 0 0.375rem; min-width: 2rem; }
    &[data-size="lg"]  { height: 2.5rem; padding: 0 0.625rem; min-width: 2.5rem; }
  }

  @media (prefers-reduced-motion: reduce) {
    .toggle { transition: none; }
  }

  @media (prefers-contrast: more) {
    .toggle {
      border: 2px solid transparent;

      &[data-variant="outline"] {
        border-color: var(--foreground);
      }

      &[aria-pressed="true"] {
        border-color: currentColor;
      }
    }
  }

  @media (forced-colors: active) {
    .toggle {
      border: 1px solid ButtonText;

      &[aria-pressed="true"] {
        background: Highlight;
        color: HighlightText;
      }

      &:disabled {
        border-color: GrayText;
        color: GrayText;
      }
    }
  }
}

JavaScript view file

Single click handler toggles aria-pressed between "true" and "false".

// -- Toggle ---------------------------------------------------
// Toggles aria-pressed on .toggle buttons.
// Skips toggles inside .toggle-group — those are managed by toggle-group.js.

function init() {
  document.querySelectorAll('.toggle:not([data-init]):not(.toggle-group .toggle)').forEach((toggle) => {
  toggle.dataset.init = '';
  toggle.addEventListener('click', () => {
    const pressed = toggle.getAttribute('aria-pressed') === 'true';
    toggle.setAttribute('aria-pressed', !pressed);
  });
});
}

init();
new MutationObserver(init).observe(document, { childList: true, subtree: true });