How to Handle State Management Within Skills in Nutlope/hallmark
Hallmark handles state management within skills through declarative CSS-driven UI states and lightweight data attributes, keeping business logic outside the skill while supporting eight canonical interaction states and optimistic updates.
The Nutlope/hallmark repository defines a skill as a pure visual and interaction layer that remains framework-agnostic. When you handle state management within skills in Nutlope/hallmark, you work with browser-native patterns rather than importing heavyweight state libraries, ensuring components remain self-contained and portable across themes.
The Philosophy Behind Hallmark Skills
Hallmark enforces strict boundaries between visual presentation and application logic. According to skills/hallmark/references/contract.md, the skill contract explicitly states that "state management, data fetching, business rules" are handled outside the skill; the skill only needs to react to the resulting DOM state. This architecture prevents skills from becoming tightly coupled to specific data sources or business logic, making them truly reusable.
Eight Canonical UI States
Every interactive element in a Hallmark skill must implement a complete set of eight standardized states defined in skills/hallmark/references/interaction-and-states.md:
- Default: The resting state of the component
- Hover: Mouse interaction (only on devices supporting hover)
- Focus: Keyboard navigation via
:focus-visible - Active/Pressed: The moment of interaction using
:active - Disabled: Non-interactive state via
[disabled]oraria-disabled - Loading: Async operation in progress using
[data-state="loading"] - Error: Failure state triggered by validation or network errors
- Success: Completion feedback such as
[data-state="copied"]
Implementing State-Driven UI
State management relies on CSS custom properties and attribute selectors rather than JavaScript class manipulation. This approach minimizes JavaScript execution and leverages the browser's rendering engine for smooth transitions.
Using Data Attributes for Transient States
JavaScript only toggles data-state attributes to communicate transient UI flags. CSS reacts to these attributes to provide visual feedback, keeping the DOM as the single source of truth.
// Copy-to-clipboard microinteraction (uses data-state)
const copyBtn = document.querySelector('.copy-btn');
copyBtn.addEventListener('click', async () => {
const text = copyBtn.dataset.value;
await navigator.clipboard.writeText(text);
copyBtn.dataset.state = 'copied';
setTimeout(() => delete copyBtn.dataset.state, 2500);
});
The corresponding CSS in skills/hallmark/references/microinteractions.md defines the visual response without requiring additional JavaScript logic:
/* Loading state via data attribute */
.btn[data-state="loading"] {
cursor: wait;
}
.btn[data-state="loading"]::after {
content: "";
display: inline-block;
width: 1rem; height: 1rem;
border: 2px solid var(--color-ink);
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
CSS-First State Transitions
Each state is expressed via CSS custom properties or pseudo-classes, avoiding transition-all and layout-shifting properties as specified in the microinteractions reference. The implementation reserves space for focus rings to prevent layout shifts:
/* Base styling */
.btn {
border: 1px solid var(--color-rule-2);
background: var(--color-paper);
outline: 2px solid transparent; /* reserve space for focus ring */
transition:
background-color var(--dur-short) var(--ease-out),
transform 100ms var(--ease-out);
}
/* Hover – only for devices that support hover */
@media (hover: hover) {
.btn:hover { background: var(--color-paper-2); }
}
/* Focus – keyboard only */
.btn:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
/* Active/pressed */
.btn:active { transform: translateY(1px); }
/* Disabled */
.btn[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
Handling User Interactions
Complex interactions follow optimistic UI patterns with automatic rollback capabilities, implemented in site/examples/**/script.js and documented in skills/hallmark/references/microinteractions.md.
Optimistic UI Updates with Rollback
Actions update the UI instantly before firing async requests. If the request fails, the interface rolls back with a short animation and presents an Undo toast via the global utility system defined in site/js/main.js:
// Optimistic toggle with rollback + Undo toast
async function toggleItem(item) {
const prev = item.completed;
item.completed = !prev; // UI updates instantly
render(); // re-render the list
try {
await api.update(item); // async persistence
} catch {
item.completed = prev; // rollback UI
render();
toast({
tone: 'error',
message: "Couldn't save.",
action: { label: 'Undo', run: () => toggleItem(item) }
});
}
}
Accessibility and Reduced Motion
All motion respects prefers-reduced-motion: reduce as a first-class state. When the media query matches, animations collapse to instant opacity changes rather than disabling transitions entirely, maintaining usability while respecting user preferences:
@media (prefers-reduced-motion: reduce) {
.btn {
transition-duration: 0.01ms;
}
}
Key Implementation Files
The following files define the complete state-management strategy for Hallmark skills:
skills/hallmark/references/interaction-and-states.md: Defines the eight required UI states and CSS recipesskills/hallmark/references/microinteractions.md: Provides motion guidelines, reduced-motion handling, and concrete JS/CSS recipesskills/hallmark/references/contract.md: Establishes that skills handle only visual/interaction concernssite/js/main.js: Entry point for global UI utilities such as the toast systemsite/examples/**/script.js: Real-world demonstrations of state toggles and loading patterns
Summary
- Hallmark skills avoid custom state machines and heavyweight frameworks by using declarative CSS and data attributes.
- Eight canonical states (default, hover, focus, active, disabled, loading, error, success) must be implemented for every interactive element.
- JavaScript only toggles
data-stateattributes; CSS handles all visual transitions and animations. - Optimistic updates provide immediate feedback with automatic rollback and Undo toast support on failure.
- Business logic lives outside the skill according to the contract defined in
skills/hallmark/references/contract.md.
Frequently Asked Questions
Does Hallmark use React or Redux for state management?
No. Hallmark deliberately avoids complex state-management libraries like Redux or even React hooks within skills. The architecture requires that state management, data fetching, and business rules exist outside the skill component. The skill only reacts to DOM state changes via CSS and minimal JavaScript attribute toggling.
How do I implement a loading state in a Hallmark skill?
Apply a data-state="loading" attribute to the target element via JavaScript. The CSS defined in skills/hallmark/references/microinteractions.md responds to this attribute to display visual feedback such as spinners or disabled cursors. Remove the attribute when the operation completes to return to the default or success state.
What happens if an optimistic update fails?
The UI immediately rolls back to the previous state and renders the original data. Simultaneously, the global toast system (available in site/js/main.js) displays an error message with an Undo action, allowing users to retry the operation without manual page refreshes.
Are animations required for Hallmark skills?
No. Hallmark treats reduced motion as a first-class state. All animations respect the prefers-reduced-motion: reduce media query by collapsing to instant opacity changes or 0.01ms transition durations, ensuring accessibility while maintaining functional 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 →