# Hallmark Component States: The 8 UI States and How to Style Them

> Discover the 8 Hallmark component states like Default Hover Active Focus-visible Disabled Loading Success and Copied Learn how to style them for consistent accessible UI with CSS pseudo-classes and data-state attributes

- Repository: [Hassan El Mghari/hallmark](https://github.com/Nutlope/hallmark)
- Tags: deep-dive
- Published: 2026-08-05

---

**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`](https://github.com/Nutlope/hallmark/blob/main/site/css/components.css) and [`site/examples/press-01/styles.css`](https://github.com/Nutlope/hallmark/blob/main/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`](https://github.com/Nutlope/hallmark/blob/main/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`

```css
/* 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:

```css
/* 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`](https://github.com/Nutlope/hallmark/blob/main/site/css/components.css) at **lines 801, 810–811**:

```css
/* 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.

```html
<!-- 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`](https://github.com/Nutlope/hallmark/blob/main/site/examples/press-01/script.js):

```javascript
// 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`](https://github.com/Nutlope/hallmark/blob/main/site/css/components.css) | Global component styles including **Copied** state | [801, 810–811](https://github.com/Nutlope/hallmark/blob/main/site/css/components.css#L801-L811) |
| [`site/examples/press-01/styles.css`](https://github.com/Nutlope/hallmark/blob/main/site/examples/press-01/styles.css) | Demo button **Loading** and **Success** states | [843–846](https://github.com/Nutlope/hallmark/blob/main/site/examples/press-01/styles.css#L843-L846) |
| [`site/examples/press-01/script.js`](https://github.com/Nutlope/hallmark/blob/main/site/examples/press-01/script.js) | JavaScript state controllers | [Full file](https://github.com/Nutlope/hallmark/blob/main/site/examples/press-01/script.js) |
| [`site/index.html`](https://github.com/Nutlope/hallmark/blob/main/site/index.html) | Component markup examples | [Full file](https://github.com/Nutlope/hallmark/blob/main/site/index.html) |

## 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`](https://github.com/Nutlope/hallmark/blob/main/site/css/components.css) for global styles, [`site/examples/press-01/styles.css`](https://github.com/Nutlope/hallmark/blob/main/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`](https://github.com/Nutlope/hallmark/blob/main/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`](https://github.com/Nutlope/hallmark/blob/main/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.