Hallmark Component States: The 8 UI States and How to Style Them
Hallmark defines eight distinct component states—Default, Hover, Active, Focus-visible, Disabled, Loading, Success, and Copied—that use either CSS pseudo-classes or data-state attributes for consistent, accessible styling.
The Hallmark design system, created by Nutlope, provides a predictable interaction model for buttons, copy-to-clipboard controls, and other interactive elements. These eight states are implemented across the source files site/css/components.css and site/examples/press-01/styles.css, ensuring visual consistency through CSS custom properties and clear JavaScript-driven state transitions.
The 8 Hallmark Component States
Hallmark components recognize four native CSS pseudo-class states and four JavaScript-controlled data-state states. Understanding this distinction is critical for proper implementation.
Native CSS Pseudo-Class States
These four states rely on standard browser behavior and require no JavaScript.
| State | Selector | Visual Treatment |
|---|---|---|
| Default | .btn |
Background: var(--color-accent); text: var(--color-accent-ink); border: var(--color-accent) |
| Hover | .btn:hover |
Background and border shift to var(--color-focus) |
| Active | .btn:active |
transform: translateY(1px) for pressed feedback |
| Focus-visible | .btn:focus-visible |
outline: 2px solid var(--color-ink) with outline-offset: 3px |
| Disabled | .btn:disabled, .btn[aria-disabled="true"] |
opacity: 0.55; cursor: not-allowed; transforms disabled |
JavaScript-Driven data-state States
These four states require JavaScript to toggle the data-state attribute, enabling complex UI feedback for async operations.
6. Loading State
Applied with data-state="loading", this state indicates an in-progress operation. In site/examples/press-01/styles.css at lines 843–844, the implementation includes:
cursor: progresson the buttonopacity: 0.6on the inner label via.btn[data-state="loading"] .btn__label
/* From site/examples/press-01/styles.css#L843-L844 */
.btn[data-state="loading"] {
cursor: progress;
}
.btn[data-state="loading"] .btn__label {
opacity: 0.6;
}
7. Success State
The data-state="success" attribute applies a neutral, completed appearance. At line 846 of the same file:
/* From site/examples/press-01/styles.css#L846 */
.btn[data-state="success"] {
background: transparent;
color: var(--color-ink);
border-color: var(--color-rule-2);
}
8. Copied State
Unique to copy-to-clipboard buttons, this transient state swaps visible labels. Implemented in site/css/components.css at lines 801, 810–811:
/* From site/css/components.css#L801-L811 */
.code__copy[data-state="copied"] .code__copy-default {
display: none;
}
.code__copy[data-state="copied"] .code__copy-done {
display: inline;
}
How to Implement Hallmark Component States
Basic HTML Structure
The markup pattern varies slightly between standard buttons and copy-to-clipboard controls.
<!-- Standard button with all state support -->
<button class="btn" type="button">
<span class="btn__label">Submit</span>
</button>
<!-- Disabled state -->
<button class="btn" disabled>Unavailable</button>
<!-- Copy-to-clipboard button with dual labels -->
<button class="code__copy" data-copy-source="example-code">
<span class="code__copy-default">Copy</span>
<span class="code__copy-done">Copied!</span>
</button>
JavaScript State Management
Toggle data-state attributes to drive the non-native states. Here's the pattern used in site/examples/press-01/script.js:
// Standard button: loading → success cycle
const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
btn.dataset.state = 'loading';
fetch('/api/submit', { method: 'POST' })
.then(() => {
delete btn.dataset.state; // clear loading
btn.dataset.state = 'success'; // show success
setTimeout(() => delete btn.dataset.state, 2000);
});
});
// Copy-to-clipboard button: transient copied state
const copyBtn = document.querySelector('.code__copy');
copyBtn.addEventListener('click', async () => {
const source = document.querySelector(copyBtn.dataset.copySource);
await navigator.clipboard.writeText(source.textContent);
copyBtn.dataset.state = 'copied';
setTimeout(() => delete copyBtn.dataset.state, 1500);
});
Hallmark Component State Styling Best Practices
Use Design Tokens for Consistency
All eight states reference CSS custom properties defined in Hallmark's token system:
--color-accentand--color-accent-inkfor default emphasis--color-focusfor hover feedback--color-inkand--color-rule-2for neutral success states
Maintain Accessibility
The focus-visible state uses outline rather than box-shadow to respect Windows High Contrast mode. The disabled state supports both the disabled attribute and aria-disabled="true" for flexible implementation with screen readers.
Keep State Transitions Brief
The Success and Copied states in Hallmark are designed as transient feedback. The source implementation automatically clears these states after 1500–2000ms, preventing permanent UI confusion.
Source Code Reference
| File | Purpose | Key Lines |
|---|---|---|
site/css/components.css |
Global component styles including Copied state | 801, 810–811 |
site/examples/press-01/styles.css |
Demo button Loading and Success states | 843–846 |
site/examples/press-01/script.js |
JavaScript state controllers | Full file |
site/index.html |
Component markup examples | Full file |
Summary
- Eight total states: five pseudo-class states (Default, Hover, Active, Focus-visible, Disabled) and three
data-stateattributes (Loading, Success, Copied). - Pseudo-class states require no JavaScript;
data-statestates need explicit JavaScript toggling. - Loading shows progress cursor and faded label; Success applies transparent background with neutral border; Copied swaps visible labels in copy buttons.
- Source files:
site/css/components.cssfor global styles,site/examples/press-01/styles.cssfor demo implementations.
Frequently Asked Questions
What is the difference between Hallmark's Disabled and Loading states?
Disabled is a permanent state preventing interaction, styled with reduced opacity (0.55) and cursor: not-allowed. Loading is transient, indicating active processing, styled with cursor: progress and a faded label. Disabled uses native :disabled or [aria-disabled="true"]; Loading requires data-state="loading" set by JavaScript.
How does Hallmark handle focus states for accessibility?
Hallmark uses :focus-visible rather than :focus, ensuring focus indicators appear only during keyboard navigation. The implementation applies a solid outline with clear offset: outline: 2px solid var(--color-ink) and outline-offset: 3px. This pattern in site/css/components.css respects user preferences including Windows High Contrast mode.
Can the Success state be used for non-button components?
While the source implementation in site/examples/press-01/styles.css demonstrates Success on buttons, the data-state="success" pattern applies to any interactive component. The visual treatment—transparent background, var(--color-ink) text, and var(--color-rule-2) border—can be adapted by applying the same CSS custom properties to other elements.
Why does Hallmark use data-state attributes instead of CSS classes for transient states?
data-state attributes provide semantic clarity and prevent class name collisions. They clearly indicate JavaScript-managed state rather than stylistic variation, making the markup more readable and the state machine explicit. This pattern also enables attribute selectors that won't conflict with utility-class architectures common in modern CSS frameworks.
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 →