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

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, 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 and is mandatory per the component-scope rules in 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 or .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:

<!-- 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

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 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).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →