# The 8 States of a Hallmark Component: Complete CSS Implementation Guide

> Master the 8 states of a Hallmark component: default, hover, focus, active, disabled, loading, error, and success. This CSS implementation guide shows you how to build robust interfaces.

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

---

**A Hallmark component must implement eight mandatory interaction states: default, hover, focus, active/pressed, disabled, loading, error, and success.**

Every interactive element in the Hallmark design system—buttons, links, form controls, and more—must account for these eight states. This requirement is explicitly documented in the *Interaction and states* reference and enforced through the CSS architecture of the project. According to the source code in `Nutlope/hallmark`, omitting any state is considered an unfinished component.

## Default State

The **default state** applies when an element is idle and unmodified.

In [`components.css`](https://github.com/Nutlope/hallmark/blob/main/components.css), the `.btn` class establishes the baseline styling:

```css
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-lg);
  font-family: var(--font-label);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-ink);
  background: transparent;
  border: var(--rule-fine) solid var(--color-ink);
  border-radius: var(--radius-pill, 0);
  min-height: 44px;
  transition: transform 100ms var(--ease-out),
              background-color var(--dur-micro) var(--ease-out),
              color var(--dur-micro) var(--ease-out),
              border-color var(--dur-micro) var(--ease-out);
  cursor: pointer;
}

```

Key characteristics include a minimum 44px touch target, smooth transitions, and theme-aware color variables.

## Hover State

The **hover state** activates when a pointing device hovers over the element. Hallmark specifically targets true mouse devices to avoid triggering hover styles on touchscreens.

Implementation in [`components.css`](https://github.com/Nutlope/hallmark/blob/main/components.css):

```css
@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    background: var(--color-ink);
    color: var(--color-paper);
    transform: translateY(-1px);
  }
}

```

- Uses the `@media (hover: hover) and (pointer: fine)` query
- Provides subtle 1px upward translation
- Swaps foreground and background colors for inverted appearance

## Focus State

The **focus state** indicates keyboard or programmatic focus. Hallmark implements this globally rather than per-component.

In [`base.css`](https://github.com/Nutlope/hallmark/blob/main/base.css), the `:focus-visible` pseudo-class ensures the focus ring appears only for keyboard navigation:

```css
:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

```

This approach prevents focus rings from appearing on mouse clicks while maintaining full keyboard accessibility.

## Active/Pressed State

The **active state** (also called **pressed**) occurs while the user is physically pressing the element.

Implementation in [`components.css`](https://github.com/Nutlope/hallmark/blob/main/components.css):

```css
.btn:active {
  transform: translateY(1px);
  transition-duration: 60ms;
}

```

- 1px downward translation creates tactile feedback
- Shortened transition duration (60ms) for snappier response
- Often paired with darker color values in production variants

## Disabled State

The **disabled state** removes interactivity from an element.

```css
.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

```

Hallmark supports both the native `disabled` attribute and `aria-disabled="true"` for accessibility. The reduced opacity (0.5) and `not-allowed` cursor provide clear visual indication.

## Loading State

The **loading state** indicates asynchronous processing. Hallmark uses `data-state` attributes to toggle this dynamically.

```css
.btn[data-state="loading"] {
  position: relative;
  color: transparent;               /* hide label */
}

.btn[data-state="loading"]::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 1rem;
  height: 1rem;
  border: 2px solid var(--color-muted);
  border-top-color: var(--color-ink);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

```

The inline spinner preserves layout stability while keeping the button structurally intact for screen readers.

## Error State

The **error state** communicates validation failures or processing errors.

```css
.btn[data-state="error"] {
  border-color: var(--color-error);
  color: var(--color-error);
}

```

- Applies semantic error color from theme system
- Often accompanied by `aria-invalid="true"` for accessibility
- May include error icons or messages depending on component complexity

## Success State

The **success state** confirms completed actions.

```css
.btn[data-state="success"] {
  border-color: var(--color-success);
  color: var(--color-success);
}

```

Common patterns include:
- Green checkmark icons
- Confirmation message overlays
- Optional auto-dismiss after timeout

## Complete Working Example

Here's a production-ready button demonstrating all eight states:

```html
<button class="btn"
        data-state="default"
        aria-live="polite"
        aria-label="Save"
        id="saveBtn">
  Save
</button>

```

```javascript
// Example state transition in site/js/main.js pattern
const btn = document.getElementById('saveBtn');

// Trigger loading
btn.setAttribute('data-state', 'loading');
btn.setAttribute('aria-disabled', 'true');

// Simulate async operation
setTimeout(() => {
  btn.setAttribute('data-state', 'success');
  btn.textContent = 'Saved!';
  
  // Auto-reset after delay
  setTimeout(() => {
    btn.setAttribute('data-state', 'default');
    btn.textContent = 'Save';
    btn.removeAttribute('aria-disabled');
  }, 2000);
}, 1500);

```

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`skills/hallmark/references/interaction-and-states.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/interaction-and-states.md) | Authoritative documentation of the eight-state requirement (lines 9-16) |
| [`site/css/components.css`](https://github.com/Nutlope/hallmark/blob/main/site/css/components.css) | Component-specific state implementations |
| [`site/css/base.css`](https://github.com/Nutlope/hallmark/blob/main/site/css/base.css) | Global focus-visible and baseline styles |
| [`site/js/main.js`](https://github.com/Nutlope/hallmark/blob/main/site/js/main.js) | Runtime state management examples |

## Summary

- **Eight states are mandatory**: default, hover, focus, active/pressed, disabled, loading, error, success
- **State transitions** use `data-state` attributes or modifier classes for JavaScript control
- **Hover detection** requires `@media (hover: hover) and (pointer: fine)` for proper device targeting
- **Focus management** is global via `:focus-visible` in [`base.css`](https://github.com/Nutlope/hallmark/blob/main/base.css)
- **Accessibility attributes** (`aria-disabled`, `aria-invalid`) must accompany visual state changes

## Frequently Asked Questions

### What happens if a Hallmark component omits one of the eight states?

A component missing any of the eight states is considered unfinished according to the Hallmark reference documentation. The [`interaction-and-states.md`](https://github.com/Nutlope/hallmark/blob/main/interaction-and-states.md) file explicitly requires all eight for production UI elements.

### How does Hallmark prevent hover states on touch devices?

The [`components.css`](https://github.com/Nutlope/hallmark/blob/main/components.css) file wraps hover styles in `@media (hover: hover) and (pointer: fine)`, which targets only devices with true hover capability and fine-grained pointing (typically mice). Touch screens fail this media query and receive no hover styles.

### Why use `data-state` attributes instead of CSS classes for loading, error, and success?

`data-state` provides semantic clarity about the element's current condition and enables straightforward JavaScript toggling without class string manipulation. This approach also separates state logic from stylistic modifiers in the CSS architecture.