# Hallmark's 8-State Checklist for Interactive Components: Complete Implementation Guide

> Master Hallmark's 8-state checklist for interactive components. This guide ensures accessibility, prevents layout shifts, and provides complete design coverage for your UI.

- Repository: [Hassan El Mghari/hallmark](https://github.com/Nutlope/hallmark)
- Tags: how-to-guide
- Published: 2026-07-19

---

**Hallmark enforces a strict requirement that every interactive component must implement eight distinct UI states—Default, Hover, Focus, Active, Disabled, Loading, Error, and Success—to ensure accessibility, prevent layout shifts, and guarantee complete design coverage.**

The `Nutlope/hallmark` repository defines this rigorous standard in its component-scope workflow. According to the project's [`SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/SKILL.md), any interactive element missing even one of these states is treated as **unfinished** and will fail the automated "8-state demo" check. This discipline ensures components ship production-ready with comprehensive visual feedback and accessibility support.

## The Eight Required States

Hallmark's eight-state checklist originates from [`skills/hallmark/references/interaction-and-states.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/interaction-and-states.md) and is mandatory per the component-scope rules in [`skills/hallmark/SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/SKILL.md) (lines 79-86). Each state demands specific visual treatment and, where applicable, ARIA attributes.

### Default

The baseline appearance at rest. Must include base styling for borders, backgrounds, and typography without relying on browser defaults alone.

### Hover

Triggered when a pointer device is over the element. **Critical constraint:** only apply on devices that support hover (`@media (hover: hover)`). Treatment includes subtle color shifts or a `1px` translate, but **never** changes to `border-width` to prevent layout shift.

### Focus (`:focus-visible`)

Keyboard or programmatic focus states require a visible focus ring of at least `2px` thickness with minimum `3:1` contrast ratio against adjacent colors. Use `:focus-visible`, not `:focus`, to avoid showing rings on mouse interaction.

### Active / Pressed

The state while the element is being pressed or activated. Implement "pressed-in" styling using darker background colors and slight downward transforms (`translateY(1px)`) to simulate physical depression.

### Disabled

When interaction is blocked. Must combine `opacity: ~0.5`, `cursor: not-allowed`, and the `aria-disabled="true"` attribute. Visual treatment alone is insufficient for accessibility compliance.

### Loading

During asynchronous operations or validation. Requires an inline spinner or progress indicator while maintaining readable labels. The component must remain in the document flow without shifting surrounding content.

### Error

Validation failures or operation errors trigger red borders (`border-color` using error tokens), error icons, and explicit error messaging. Must include `aria-invalid="true"` on the control.

### Success

Completed operations display green check icons (`✓`) with subtle success styling. May include auto-dismissible notifications, but must maintain persistence long enough for screen reader announcement.

## Implementation Requirements

Meeting the checklist requires more than CSS pseudo-classes. The workflow mandates a **preview file** ([`.preview.html`](https://github.com/Nutlope/hallmark/blob/main/.preview.html) or [`.preview.tsx`](https://github.com/Nutlope/hallmark/blob/main/.preview.tsx)) that renders all eight states simultaneously in a vertical stack.

### The Dual-Class Strategy

Production code uses real pseudo-classes (`:hover`, `:focus-visible`, `:active`, `[disabled]`), but the preview file relies on **helper classes** (`.is-hover`, `.is-focus`, `.is-active`, `.is-disabled`) to force states for static display. This approach allows designers and QA to verify all treatments without interacting with the component.

### Mandatory Preview File Structure

The demo wrapper generates an additional preview file following this pattern:

```html
<!-- Component.preview.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Component – 8-state demo</title>
  <style>
    .btn {
      display: inline-block;
      padding: 0.5rem 1rem;
      border: 1px solid var(--color-rule-2);
      background: var(--color-paper);
      color: var(--color-ink);
      cursor: pointer;
      transition: background 0.15s;
    }
    /* Real pseudo-classes for production */
    .btn:hover,
    .btn.is-hover { background: var(--color-paper-2); }

    .btn:focus-visible,
    .btn.is-focus { 
      outline: 2px solid var(--color-focus); 
      outline-offset: 2px; 
    }

    .btn:active,
    .btn.is-active { 
      background: var(--color-paper-3); 
      transform: translateY(1px); 
    }

    .btn[disabled],
    .btn.is-disabled { 
      opacity: 0.5; 
      cursor: not-allowed; 
    }

    /* Stateful attributes for non-pseudo states */
    .btn[data-state="loading"]::after { content: "⏳ Working…"; }
    .btn[data-state="error"] { border-color: var(--color-error); }
    .btn[data-state="error"]::after { content: "⚠ Try again"; }
    .btn[data-state="success"] { border-color: var(--color-success); }
    .btn[data-state="success"]::after { content: "✓ Saved"; }
  </style>
</head>
<body>
  <div class="demo">
    <div>Default: <button class="btn">Click me</button></div>
    <div>Hover: <button class="btn is-hover">Click me</button></div>
    <div>Focus: <button class="btn is-focus">Click me</button></div>
    <div>Active: <button class="btn is-active">Click me</button></div>
    <div>Disabled: <button class="btn" disabled>Click me</button></div>
    <div>Loading: <button class="btn" data-state="loading">Click me</button></div>
    <div>Error: <button class="btn" data-state="error">Click me</button></div>
    <div>Success: <button class="btn" data-state="success">Click me</button></div>
  </div>
</body>
</html>

```

## Why Layout Stability Matters

The checklist explicitly forbids changes to `border-width` or `box-sizing` properties between states. This prevents **cumulative layout shift (CLS)** when users hover or focus elements. The `transform` property (used for Active state's `translateY`) is preferred because it does not trigger reflow of surrounding elements.

## Key Reference Files

- **[`skills/hallmark/references/interaction-and-states.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/interaction-and-states.md)**: Defines state triggers, contrast requirements, and visual treatments.
- **[`skills/hallmark/SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/SKILL.md)**: Documents the component-scope rule requiring all eight states and describes the demo wrapper format.
- **Component preview files**: Generated automatically (e.g., [`Button.preview.html`](https://github.com/Nutlope/hallmark/blob/main/Button.preview.html)) to demonstrate compliance before delivery.

## Summary

- **Eight mandatory states**: Default, Hover, Focus, Active, Disabled, Loading, Error, and Success.
- **Dual implementation**: Use real pseudo-classes for production, helper classes (`.is-*`) for preview files.
- **Accessibility requirements**: Focus rings need ≥2px thickness with ≥3:1 contrast; Disabled and Error states require `aria-disabled` and `aria-invalid`.
- **Layout safety**: Avoid `border-width` changes; use `transform` and `opacity` for visual feedback.
- **Verification**: Components must ship with a [`.preview.html`](https://github.com/Nutlope/hallmark/blob/main/.preview.html) file showing all eight states stacked vertically.

## Frequently Asked Questions

### What happens if a component is missing one of the eight states?

Hallmark treats the component as **unfinished** and the build will fail the "8-state demo" check. The component-scope workflow requires the preview file to render all states before delivery is approved.

### Why use helper classes like `.is-hover` alongside real pseudo-classes?

Helper classes allow the static preview file to display all eight states simultaneously for design review and QA testing. Real pseudo-classes (`:hover`, `:focus-visible`) only activate during user interaction, making them unsuitable for automated visual regression testing in a static document.

### Which ARIA attributes are required for specific states?

**Disabled** states require `aria-disabled="true"`, while **Error** states require `aria-invalid="true"`. **Loading** states should use `aria-busy="true"` on the container and maintain focus management to announce progress to screen reader users.

### Can the 8-state checklist apply to non-button components?

Yes. The checklist applies to **every interactive component** including links, form inputs, toggles, and custom widgets. Any element that accepts user interaction must implement all eight states with appropriate treatments (for example, text inputs use focus rings and error borders, while links use active states for click feedback).