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: progress on the button
  • opacity: 0.6 on 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-accent and --color-accent-ink for default emphasis
  • --color-focus for hover feedback
  • --color-ink and --color-rule-2 for 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-state attributes (Loading, Success, Copied).
  • Pseudo-class states require no JavaScript; data-state states 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.css for global styles, site/examples/press-01/styles.css for 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:

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 →