Component Variants, States, and Accessibility

A reusable component should have clear base styles, supported variants, and interaction states. Sass maps and loops can generate predictable variant classes, while mixins can standardize recurring focus and responsive behavior.

Interactive states include hover, focus-visible, active, disabled, loading, success, and error. Not every component requires every state, but supported states should be defined consistently.

Accessibility should be considered in the underlying CSS design. Keyboard focus must remain visible, text contrast should be sufficient, and information should not be conveyed through color alone. Disabled and loading states should also be reflected appropriately in the component's HTML and application logic.

Keep component classes stable and avoid selectors that depend excessively on the exact internal markup hierarchy.

Reusable Button Variants
$button-variants: (
  primary: #2457c5,
  success: #218838,
  danger: #c62828
);

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  border: 0;
  border-radius: 0.5rem;
  font: inherit;
  cursor: pointer;

  &:focus-visible {
    outline: 3px solid #222;
    outline-offset: 3px;
  }

  &:disabled {
    opacity: 0.55;
    cursor: not-allowed;
  }
}

@each $name, $color in $button-variants {
  .button--#{$name} {
    background: $color;
    color: white;
  }
}
💡 The base class defines common layout and interaction styles. The loop creates one visual variant for every map entry. A real design system should also validate contrast for each variant and account for hover, active, and focus states. HTML button elements should use the actual disabled attribute when the action is unavailable.
Knowledge Check

Which pseudo-class is particularly appropriate for showing a keyboard focus indicator?