playbook/ui-ux-pro-max/.claude/skills/design-system/references/states-and-variants.md

4.7 KiB

States and Variants

Component state definitions and variant patterns.

Interactive States

State Definitions

State Trigger Visual Change
default None Base appearance
hover Mouse over Slight color shift
focus Tab/click Focus ring
active Mouse down Darkest color
disabled disabled attr Reduced opacity
loading Async action Spinner + opacity

State Priority

When multiple states apply, priority (highest to lowest):

  1. disabled
  2. loading
  3. active
  4. focus
  5. hover
  6. default

State Transitions

/* Standard transition for interactive elements */
.interactive {
  transition-property: color, background-color, border-color, box-shadow;
  transition-duration: var(--duration-fast);
  transition-timing-function: ease-in-out;
}
Transition Duration Easing
Color changes 150ms ease-in-out
Background 150ms ease-in-out
Transform 200ms ease-out
Opacity 150ms ease
Shadow 200ms ease-out

Focus States

Focus Ring Spec

/* Standard focus ring */
.focusable:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--ring-offset) var(--color-background),
              0 0 0 calc(var(--ring-offset) + var(--ring-width)) var(--ring-color);
}
Property Value
Ring width 2px
Ring offset 2px
Ring color primary (blue-500)
Offset color background

Focus Within

/* Container focus when child is focused */
.container:focus-within {
  border-color: var(--color-ring);
}

Disabled States

Visual Treatment

.disabled {
  opacity: var(--opacity-disabled); /* 0.5 */
  pointer-events: none;
  cursor: not-allowed;
}
Property Disabled Value
Opacity 50%
Pointer events none
Cursor not-allowed
Background muted
Color muted-foreground

Accessibility

  • Use aria-disabled="true" for semantic disabled
  • Use disabled attribute for form elements
  • Maintain sufficient contrast (3:1 minimum)

Loading States

Spinner Placement

Component Spinner Position
Button Replace icon or center
Input Trailing position
Card Center overlay
Page Center of viewport

Loading Treatment

.loading {
  position: relative;
  pointer-events: none;
}

.loading::after {
  content: '';
  /* spinner styles */
}

.loading > * {
  opacity: 0.7;
}

Error States

Visual Indicators

.error {
  border-color: var(--color-error);
  color: var(--color-error);
}

.error:focus-visible {
  box-shadow: 0 0 0 2px var(--color-background),
              0 0 0 4px var(--color-error);
}
Element Error Treatment
Input border red-500
Input focus ring red/20%
Helper text red-600
Icon red-500

Error Messages

  • Position below input
  • Use error color
  • Include icon for accessibility
  • Clear on valid input

Variant Patterns

Color Variants

/* Pattern for color variants */
.component {
  --component-bg: var(--color-primary);
  --component-fg: var(--color-primary-foreground);
  background: var(--component-bg);
  color: var(--component-fg);
}

.component.secondary {
  --component-bg: var(--color-secondary);
  --component-fg: var(--color-secondary-foreground);
}

.component.destructive {
  --component-bg: var(--color-destructive);
  --component-fg: var(--color-destructive-foreground);
}

Size Variants

/* Pattern for size variants */
.component {
  --component-height: 40px;
  --component-padding: var(--space-4);
  --component-font: var(--font-size-sm);
}

.component.sm {
  --component-height: 32px;
  --component-padding: var(--space-3);
  --component-font: var(--font-size-xs);
}

.component.lg {
  --component-height: 48px;
  --component-padding: var(--space-6);
  --component-font: var(--font-size-base);
}

Accessibility Requirements

Color Contrast

Element Minimum Ratio
Normal text 4.5:1
Large text (18px+) 3:1
UI components 3:1
Focus indicator 3:1

State Indicators

  • Never rely on color alone
  • Use icons, text, or patterns
  • Ensure focus is visible
  • Provide loading announcements

ARIA States

<!-- Disabled -->
<button disabled aria-disabled="true">Submit</button>

<!-- Loading -->
<button aria-busy="true" aria-describedby="loading-text">
  <span id="loading-text" class="sr-only">Loading...</span>
</button>

<!-- Error -->
<input aria-invalid="true" aria-describedby="error-msg">
<span id="error-msg" role="alert">Error message</span>