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
skills/hallmark/references/interaction-and-states.md: Defines state triggers, contrast requirements, and visual treatments.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) 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-disabledandaria-invalid. - Layout safety: Avoid
border-widthchanges; usetransformandopacityfor visual feedback. - Verification: Components must ship with a
.preview.htmlfile 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →