Pseudo-State Modifiers
usePseudoStateModifiers is included in the Modifiers Preset (useModifiersPreset) and you can configure it through the preset's pseudoStates option. For most projects, applying it via the preset is the recommended approach.Overview
Pseudo-state modifiers let you apply utility styles conditionally based on user interaction states. They wrap utility declarations in CSS pseudo-class selectors like :hover, :focus, and :active, generating variant utility classes that respond to user behavior.
Why Use Pseudo-State Modifiers?
Pseudo-state modifiers help you:
- Create interactive UIs: Apply styles when users hover, focus, or click on elements
- Build accessible interfaces: Use
focus-visiblefor keyboard-only focus indicators - Generate state variants: Automatically create hover, focus, and active versions of any utility
- Keep styling declarative: Express interactive states directly in your HTML class names
useHoverModifier
The useHoverModifier() function creates a modifier that applies styles when the user hovers over an element.
import { styleframe } from "styleframe";
import { useColorDesignTokens } from "@styleframe/theme";
import { useBackgroundColorUtility } from "@styleframe/theme";
import { useHoverModifier } from "@styleframe/theme";
const s = styleframe();
const { ref } = s;
const { colorPrimary, colorSecondary } = useColorDesignTokens(s, {
primary: '#006cff',
secondary: '#6c757d',
} as const);
const hover = useHoverModifier(s);
useBackgroundColorUtility(s, {
primary: ref(colorPrimary),
secondary: ref(colorSecondary),
}, [hover]);
export default s;
._background-color\:primary { background-color: var(--color--primary); }
._background-color\:secondary { background-color: var(--color--secondary); }
._hover\:background-color\:primary {
&:hover { background-color: var(--color--primary); }
}
._hover\:background-color\:secondary {
&:hover { background-color: var(--color--secondary); }
}
<!-- Change background on hover -->
<button class="_background-color:secondary _hover:background-color:primary">
Hover me
</button>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
hover | &:hover |
useFocusModifier
The useFocusModifier() function creates a modifier that applies styles when an element receives focus.
import { styleframe } from "styleframe";
import { useRingUtility } from "@styleframe/theme";
import { useFocusModifier } from "@styleframe/theme";
const s = styleframe();
const focus = useFocusModifier(s);
useRingUtility(s, {
primary: '0 0 0 2px #006cff',
}, [focus]);
export default s;
._ring\:primary { box-shadow: 0 0 0 2px #006cff; }
._focus\:ring\:primary {
&:focus { box-shadow: 0 0 0 2px #006cff; }
}
<!-- Add focus ring on focus -->
<input class="_focus:ring:primary" type="text" placeholder="Focus me">
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
focus | &:focus |
useFocusWithinModifier
The useFocusWithinModifier() function creates a modifier that applies styles when any child element receives focus.
import { styleframe } from "styleframe";
import { useBorderColorUtility } from "@styleframe/theme";
import { useFocusWithinModifier } from "@styleframe/theme";
const s = styleframe();
const focusWithin = useFocusWithinModifier(s);
useBorderColorUtility(s, {
primary: '#006cff',
}, [focusWithin]);
export default s;
._border-color\:primary { border-color: #006cff; }
._focus-within\:border-color\:primary {
&:focus-within { border-color: #006cff; }
}
<!-- Highlight form group when any input is focused -->
<div class="_focus-within:border-color:primary">
<input type="text" placeholder="Focus any input">
<input type="text" placeholder="Or this one">
</div>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
focus-within | &:focus-within |
useFocusVisibleModifier
The useFocusVisibleModifier() function creates a modifier that applies styles only when focus is visible (typically keyboard navigation).
import { styleframe } from "styleframe";
import { useOutlineUtility } from "@styleframe/theme";
import { useFocusVisibleModifier } from "@styleframe/theme";
const s = styleframe();
const focusVisible = useFocusVisibleModifier(s);
useOutlineUtility(s, {
primary: '2px solid #006cff',
}, [focusVisible]);
export default s;
._outline\:primary { outline: 2px solid #006cff; }
._focus-visible\:outline\:primary {
&:focus-visible { outline: 2px solid #006cff; }
}
<!-- Show outline only for keyboard focus -->
<button class="_focus-visible:outline:primary">
Tab to me
</button>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
focus-visible | &:focus-visible |
useFocusVisibleModifier over useFocusModifier for focus indicators. It only shows the indicator for keyboard navigation, not mouse clicks, providing a cleaner user experience.useActiveModifier
The useActiveModifier() function creates a modifier that applies styles when an element is being activated (clicked/pressed).
import { styleframe } from "styleframe";
import { useScaleUtility } from "@styleframe/theme";
import { useActiveModifier } from "@styleframe/theme";
const s = styleframe();
const active = useActiveModifier(s);
useScaleUtility(s, {
95: '0.95',
}, [active]);
export default s;
._scale\:95 { scale: 0.95; }
._active\:scale\:95 {
&:active { scale: 0.95; }
}
<!-- Scale down on click -->
<button class="_active:scale:95">Press me</button>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
active | &:active |
useVisitedModifier
The useVisitedModifier() function creates a modifier that applies styles to visited links.
import { styleframe } from "styleframe";
import { useTextColorUtility } from "@styleframe/theme";
import { useVisitedModifier } from "@styleframe/theme";
const s = styleframe();
const visited = useVisitedModifier(s);
useTextColorUtility(s, {
muted: '#6c757d',
}, [visited]);
export default s;
._text-color\:muted { color: #6c757d; }
._visited\:text-color\:muted {
&:visited { color: #6c757d; }
}
<!-- Style visited links -->
<a href="/page" class="_visited:text-color:muted">Visited link</a>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
visited | &:visited |
useTargetModifier
The useTargetModifier() function creates a modifier that applies styles when the element is the target of the current URL fragment.
import { styleframe } from "styleframe";
import { useBackgroundColorUtility } from "@styleframe/theme";
import { useTargetModifier } from "@styleframe/theme";
const s = styleframe();
const target = useTargetModifier(s);
useBackgroundColorUtility(s, {
highlight: '#fff3cd',
}, [target]);
export default s;
._background-color\:highlight { background-color: #fff3cd; }
._target\:background-color\:highlight {
&:target { background-color: #fff3cd; }
}
<!-- Highlight section when navigated to via anchor -->
<section id="section-1" class="_target:background-color:highlight">
<h2>Section 1</h2>
</section>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
target | &:target |
usePseudoStateModifiers
The usePseudoStateModifiers() function registers all pseudo-state modifiers at once and returns them as a destructurable object.
import { styleframe } from "styleframe";
import { useColorDesignTokens } from "@styleframe/theme";
import { useOpacityUtility } from "@styleframe/theme";
import { usePseudoStateModifiers } from "@styleframe/theme";
const s = styleframe();
const { hover, focus, active } = usePseudoStateModifiers(s);
useOpacityUtility(s, {
75: '0.75',
50: '0.5',
}, [hover, focus, active]);
export default s;
._opacity\:75 { opacity: 0.75; }
._opacity\:50 { opacity: 0.5; }
._hover\:opacity\:75 { &:hover { opacity: 0.75; } }
._hover\:opacity\:50 { &:hover { opacity: 0.5; } }
._focus\:opacity\:75 { &:focus { opacity: 0.75; } }
._focus\:opacity\:50 { &:focus { opacity: 0.5; } }
._active\:opacity\:75 { &:active { opacity: 0.75; } }
._active\:opacity\:50 { &:active { opacity: 0.5; } }
<button class="_opacity:75 _hover:opacity:50 _active:opacity:50">
Interactive button
</button>
Returned Modifiers
| Key | Modifier Name | CSS Selector |
|---|---|---|
hover | hover | &:hover |
focus | focus | &:focus |
focusWithin | focus-within | &:focus-within |
focusVisible | focus-visible | &:focus-visible |
active | active | &:active |
visited | visited | &:visited |
target | target | &:target |
Examples
Interactive Button
import { styleframe } from "styleframe";
import { useColorDesignTokens } from "@styleframe/theme";
import { useBackgroundColorUtility, useScaleUtility, useOpacityUtility } from "@styleframe/theme";
import { usePseudoStateModifiers } from "@styleframe/theme";
const s = styleframe();
const { ref } = s;
const { colorPrimary } = useColorDesignTokens(s, { primary: '#006cff' } as const);
const { hover, active, focusVisible } = usePseudoStateModifiers(s);
useBackgroundColorUtility(s, {
primary: ref(colorPrimary),
}, [hover]);
useOpacityUtility(s, {
90: '0.9',
}, [hover]);
useScaleUtility(s, {
95: '0.95',
}, [active]);
export default s;
<button class="_background-color:primary _hover:opacity:90 _active:scale:95">
Click me
</button>
Focus Ring with Focus-Visible
import { styleframe } from "styleframe";
import { useRingUtility, useOutlineUtility } from "@styleframe/theme";
import { useFocusVisibleModifier } from "@styleframe/theme";
const s = styleframe();
const focusVisible = useFocusVisibleModifier(s);
useRingUtility(s, {
primary: '0 0 0 3px rgba(0, 108, 255, 0.5)',
}, [focusVisible]);
useOutlineUtility(s, {
none: 'none',
}, [focusVisible]);
export default s;
<button class="_focus-visible:ring:primary _focus-visible:outline:none">
Keyboard accessible
</button>
Best Practices
- Prefer
focus-visibleoverfocus: UseuseFocusVisibleModifierfor focus indicators to avoid showing focus rings on mouse clicks - Combine
hoverandactive: Create buttons that respond to both hover and press states for better feedback - Use
focus-withinfor form groups: Highlight parent containers when any child input is focused - Limit modifier count: Only generate the state variants your design requires to keep CSS bundle size small
- Test touch devices:
:hovermay behave unexpectedly on touch devices; design fallbacks accordingly
FAQ
:focus triggers on all focus events (mouse click, keyboard navigation, programmatic focus). :focus-visible only triggers when the browser determines focus should be visible, typically during keyboard navigation. Use focus-visible for focus indicators and focus for functional styling changes.[hover, dark] generates _hover:property:value, _dark:property:value, and _dark:hover:property:value.:hover is typically triggered on tap and persists until the user taps elsewhere. For touch-specific behavior, consider using JavaScript event handlers instead.:focus applies to the focused element itself. :focus-within applies to an element when it or any of its descendants receive focus. This is useful for highlighting parent containers, form groups, or dropdown menus when a child element is focused.