Pseudo-State
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 { useColor } from "@styleframe/theme";
import { useBackgroundColorUtility } from "@styleframe/theme";
import { useHoverModifier } from "@styleframe/theme";
const s = styleframe();
const { ref } = s;
const { colorPrimary, colorSecondary } = useColor(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 { useColor } 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 { useColor } from "@styleframe/theme";
import { useBackgroundColorUtility, useScaleUtility, useOpacityUtility } from "@styleframe/theme";
import { usePseudoStateModifiers } from "@styleframe/theme";
const s = styleframe();
const { ref } = s;
const { colorPrimary } = useColor(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.