API Essentials

Utility Modifiers

Styleframe utility modifiers transform utility declarations to create conditional variations like hover states, media queries, and breakpoints. Build composable utility variants with full type safety.

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.
styleframe.config.ts
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;

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:

styleframe.config.ts
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;

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:

styleframe.config.ts
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;
Pro tip: Multi-key modifiers are ideal for mutually exclusive conditions like responsive breakpoints, theme variations, or directional (LTR/RTL) variants.

Examples

Media Query Modifier

Create a dark mode modifier using a media query:

styleframe.config.ts
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;

Combining Multiple Modifiers

Pass multiple modifiers to a utility creator to generate all individual and combined variations:

styleframe.config.ts
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;
Good to know: When multiple modifiers are combined, Styleframe automatically generates all possible combinations in alphabetical order, giving you fine-grained control over when styles are applied.

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