Utility Modifiers
Overview
Utility modifiers are reusable functions that wrap utility declarations in conditional selectors. They generate state-based, responsive, and contextual variants of your utility classes, without duplicating the underlying styling logic.
You define a utility modifier once, then pass it to any number of utility creators. Styleframe handles the class name generation and CSS output for each combination.
Why use utility modifiers?
Utility modifiers help you:
- Add conditional styling: Generate hover, focus, active, and other state-based utility variants from a single definition.
- Build responsive utilities: Create breakpoint-specific variants using media query modifiers.
- Compose variations: Combine multiple modifiers to produce fine-grained utility variants (e.g., dark mode + hover).
- Reduce duplication: Define transformation logic once and reuse it across your entire utility system.
- Maintain type safety: Get full auto-complete and type checking for modifier keys and combinations.
Defining Modifiers
Define modifiers using the modifier() function from your styleframe instance. The function takes a key (the modifier name) and a factory function that transforms the utility declarations.
The factory function receives a context object that includes:
declarations: The CSS declarations generated by the utility factory function.- Other declaration context helper functions such as
selector,variable,ref,media, and more.
import { styleframe } from "styleframe";
const s = styleframe();
const { utility, variable, ref, modifier } = s;
const colorPrimary = variable("color.primary", "#007bff");
// Define a hover modifier
const hover = modifier("hover", ({ declarations }) => ({
[`&:hover`]: declarations,
}));
const createBackgroundUtility = utility("background", ({ value }) => ({
background: value,
}));
// Pass the modifier as the second argument to the utility creator
createBackgroundUtility(
{
primary: ref(colorPrimary),
},
[hover],
);
export default s;
:root {
--color--primary: #007bff;
}
._background\:primary {
background: var(--color--primary);
}
._hover\:background\:primary {
&:hover {
background: var(--color--primary);
}
}
The generated modifier class name follows the format _modifier:property:value (e.g., _hover:background:primary).
Using the Context Object
The modifier factory function receives a full context object for advanced use cases. In addition to declarations, the context includes:
variables: An array of variables defined in the utility factory function.children: An array of child selectors generated by the utility factory function.selector(),variable(),ref(),media(), and other declaration context helpers.
Use the selector() helper when you need to forward variables and children alongside declarations:
import { styleframe } from "styleframe";
const s = styleframe();
const { utility, variable, ref, modifier } = s;
const colorPrimary = variable("color.primary", "#007bff");
const hover = modifier(
"hover",
({ declarations, variables, children, selector }) => {
// Forward declarations, variables, and children into a hover selector
selector("&:hover", { declarations, variables, children });
},
);
const createBackgroundUtility = utility("background", ({ value }) => ({
background: value,
}));
createBackgroundUtility(
{
primary: ref(colorPrimary),
},
[hover],
);
export default s;
:root {
--color--primary: #007bff;
}
._background\:primary {
background: var(--color--primary);
}
._hover\:background\:primary {
&:hover {
background: var(--color--primary);
}
}
Multi-key Modifiers
Pass an array of keys to create modifiers that generate one variant per key. This is useful for mutually exclusive conditions like responsive breakpoints or directional variants.
Each call to the factory function receives the current key, which you can use to determine the condition:
import { styleframe, valueOf } from "styleframe";
import { useBreakpoints } from "@styleframe/theme";
const s = styleframe();
const { utility, variable, ref, modifier } = s;
const { breakpointSm, breakpointMd, breakpointLg } = useBreakpoints(s);
const breakpoints = {
sm: valueOf(breakpointSm),
md: valueOf(breakpointMd),
lg: valueOf(breakpointLg),
};
// Multi-key modifier: one variant per breakpoint
const breakpointsMax = modifier(
["max-sm", "max-md", "max-lg"],
({ key, declarations }) => ({
[`@media screen and (max-width: ${
breakpoints[key.replace("max-", "")]
})`]: declarations,
}),
);
const createHiddenUtility = utility("hidden", () => ({
display: "none",
}));
createHiddenUtility({ default: undefined }, [breakpointsMax]);
export default s;
:root {
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
}
._hidden {
display: none;
}
._max-sm\:hidden {
@media screen and (max-width: 640px) {
display: none;
}
}
._max-md\:hidden {
@media screen and (max-width: 768px) {
display: none;
}
}
._max-lg\:hidden {
@media screen and (max-width: 1024px) {
display: none;
}
}
Examples
Media Query Modifier
Create a dark mode modifier using a media query:
import { styleframe } from "styleframe";
const s = styleframe();
const { utility, variable, ref, modifier } = s;
const colorPrimary = variable("color.primary", "#007bff");
const colorSecondary = variable("color.secondary", "#6c757d");
// Media query modifier for dark mode
const dark = modifier("dark", ({ declarations }) => ({
["@media (prefers-color-scheme: dark)"]: declarations,
}));
const createBackgroundUtility = utility("background", ({ value }) => ({
background: value,
}));
createBackgroundUtility(
{
primary: ref(colorPrimary),
secondary: ref(colorSecondary),
},
[dark],
);
export default s;
:root {
--color--primary: #007bff;
--color--secondary: #6c757d;
}
._background\:primary {
background: var(--color--primary);
}
._background\:secondary {
background: var(--color--secondary);
}
._dark\:background\:primary {
@media (prefers-color-scheme: dark) {
background: var(--color--primary);
}
}
._dark\:background\:secondary {
@media (prefers-color-scheme: dark) {
background: var(--color--secondary);
}
}
Combining Multiple Modifiers
Pass multiple modifiers to a utility creator to generate all individual and combined variations:
import { styleframe } from "styleframe";
const s = styleframe();
const { utility, variable, ref, modifier } = s;
const colorPrimary = variable("color.primary", "#007bff");
const colorSecondary = variable("color.secondary", "#6c757d");
// Define multiple modifiers
const hover = modifier("hover", ({ declarations }) => ({
[`&:hover`]: declarations,
}));
const dark = modifier("dark", ({ declarations }) => ({
["@media (prefers-color-scheme: dark)"]: declarations,
}));
const focus = modifier("focus", ({ declarations }) => ({
[`&:focus`]: declarations,
}));
const createBackgroundUtility = utility("background", ({ value }) => ({
background: value,
}));
// Apply multiple modifiers to the same utility
createBackgroundUtility(
{
primary: ref(colorPrimary),
secondary: ref(colorSecondary),
},
[hover, dark, focus],
);
export default s;
:root {
--color--primary: #007bff;
--color--secondary: #6c757d;
}
/* Base utilities */
._background\:primary {
background: var(--color--primary);
}
._background\:secondary {
background: var(--color--secondary);
}
/* Hover variations */
._hover\:background\:primary {
&:hover {
background: var(--color--primary);
}
}
._hover\:background\:secondary {
&:hover {
background: var(--color--secondary);
}
}
/* Dark mode variations */
._dark\:background\:primary {
@media (prefers-color-scheme: dark) {
background: var(--color--primary);
}
}
._dark\:background\:secondary {
@media (prefers-color-scheme: dark) {
background: var(--color--secondary);
}
}
/* Focus variations */
._focus\:background\:primary {
&:focus {
background: var(--color--primary);
}
}
._focus\:background\:secondary {
&:focus {
background: var(--color--secondary);
}
}
/* Combined modifier variations (dark + hover, dark + focus, etc.) */
._dark\:hover\:background\:primary {
@media (prefers-color-scheme: dark) {
&:hover {
background: var(--color--primary);
}
}
}
/* ... and more combinations */
Best Practices
- Keep modifiers focused: Each modifier should handle a single condition (e.g., one pseudo-class or one media query).
- Define once, reuse everywhere: Create common modifiers (hover, focus, dark mode) once and pass them to multiple utility creators.
- Use multi-key modifiers for mutually exclusive states: Breakpoints, themes, and directional variants work well as multi-key modifiers.
- Be mindful of combinations: Each additional modifier multiplies the number of generated utility classes. Only combine modifiers that you need.
- Test modifier combinations: Verify that complex modifier combinations produce the expected CSS output using browser developer tools.
- Create composables: Group related modifiers into composable functions for better organization and reusability.
FAQ
hover) apply the same transformation for every utility value. Multi-key modifiers (like responsive breakpoints) apply different transformations based on the specific key, making them ideal for mutually exclusive conditions.Utilities
Styleframe utilities provide reusable, single-purpose CSS classes with full type safety and recipe support. Build consistent, maintainable styling systems with atomic design patterns.
Themes
Styleframe themes provide powerful design system theming capabilities with type-safe variable overrides. Create consistent light/dark modes, brand variants, and user personalization with ease.