Utilities
Overview
Utilities in Styleframe are reusable, single-purpose CSS classes that provide a foundation for atomic design patterns. They offer full type safety, auto-complete, and seamless integration with recipes to create flexible, maintainable styling systems that scale with your project.
Why use utilities?
Utilities help you:
- Build atomic design patterns: Create small, focused CSS classes that do one thing well and can be composed together.
- Maintain consistency: Ensure uniform spacing, colors, and typography across your entire application.
- Enable rapid prototyping: Quickly build interfaces by combining pre-defined utility classes.
- Integrate with recipes: Seamlessly work with responsive, state, and custom variants for maximum flexibility.
Defining Utilities
You define utilities using the utility()
function from your styleframe instance:
import { styleframe } from 'styleframe';
const s = styleframe();
const { variable, ref, utility } = s;
const spacing = variable('spacing', '1rem');
const spacingSm = variable('spacing--sm', '0.75rem');
const spacingMd = variable('spacing--md', ref(spacing));
const spacingLg = variable('spacing--lg', '1.25rem');
const createMarginUtility = utility('margin', (value) => ({
margin: value
}));
createMarginUtility({
'default': ref(spacing),
'sm': ref(spacingSm),
'md': ref(spacingMd),
'lg': ref(spacingLg),
});
export default s;
:root {
--spacing: 1rem;
--spacing--sm: 0.75rem;
--spacing--md: var(--spacing);
--spacing--lg: 1.25rem;
}
._margin {
margin: var(--spacing);
}
._margin\:sm {
margin: var(--spacing--sm);
}
._margin\:md {
margin: var(--spacing--md);
}
._margin\:lg {
margin: var(--spacing--lg);
}
The utility()
function takes a base name, a generator function, and an optional options object. The base name is used to generate the utility class names, while the generator function defines how the utility should be applied.
The function returns a utility creator that you can call with an object containing value mappings. Each key in the object corresponds to a utility class, and the value is the CSS declaration to apply.
kebab-case
, camelCase
, or PascalCase
. When generating the utility classes, Styleframe will automatically convert them to kebab-case
for consistency.The default generated utility class format is _property-name:value
, which is intuitive and CSS-like. You can customize this format in your styleframe configuration if needed.
You're absolutely right! I should maintain consistency with the existing pattern. Here's the corrected version that adds modifier
to the destructuring:
Defining Modifiers
Modifiers are functions that transform utility declarations to create variations based on conditions like hover states, media queries, or theme variations. They allow you to generate multiple related utility classes from a single base utility definition, extending your utilities with powerful conditional styling.
import { styleframe } from 'styleframe';
const s = styleframe();
const { utility, variable, ref, modifier } = s;
const colorPrimary = variable('color-primary', '#007bff');
const hover = modifier('hover', ({ declarations }) => ({
[`&:hover`]: declarations
}));
const createBackgroundUtility = utility('background', (value) => ({
background: value
}));
createBackgroundUtility({
'primary': ref(colorPrimary),
}, {
modifiers: [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
You can create modifiers that apply one of multiple keys, useful such as responsive breakpoints or custom conditions:
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),
}
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 }, {
modifiers: [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
Layout Utilities
Build flexible layout systems with display and positioning utilities:
import { styleframe } from 'styleframe';
const s = styleframe();
const { utility } = s;
const createDisplayUtility = utility('display', (value) => ({
display: value,
}));
createDisplayUtility({
'block': 'block',
'inline-block': 'inline-block',
'inline': 'inline',
'flex': 'flex',
'inline-flex': 'inline-flex',
'grid': 'grid',
'none': 'none',
});
const createFlexDirectionUtility = utility('flex-direction', (value) => ({
flexDirection: value,
}));
createFlexDirectionUtility({
'row': 'row',
'column': 'column',
});
const createJustifyContentUtility = utility('justify-content', (value) => ({
justifyContent: value,
}));
createJustifyContentUtility({
'start': 'flex-start',
'center': 'center',
'end': 'flex-end',
'between': 'space-between',
});
const createAlignItemsUtility = utility('align-items', (value) => ({
alignItems: value,
}));
createAlignItemsUtility({
'start': 'flex-start',
'center': 'center',
'end': 'flex-end',
'stretch': 'stretch',
});
export default s;
._display\:block { display: block; }
._display\:inline-block { display: inline-block; }
._display\:inline { display: inline; }
._display\:flex { display: flex; }
._display\:inline-flex { display: inline-flex; }
._display\:grid { display: grid; }
._display\:none { display: none; }
._flex-direction\:row { flex-direction: row; }
._flex-direction\:column { flex-direction: column; }
._justify-content\:start { justify-content: flex-start; }
._justify-content\:center { justify-content: center; }
._justify-content\:end { justify-content: flex-end; }
._justify-content\:between { justify-content: space-between; }
._align-items\:start { align-items: flex-start; }
._align-items\:center { align-items: center; }
._align-items\:end { align-items: flex-end; }
._align-items\:stretch { align-items: stretch; }
Complex Utilities
Create sophisticated utility systems that handle complex CSS properties and calculations:
import { styleframe, css } from 'styleframe';
const s = styleframe();
const { variable, ref, utility } = s;
const createGridColumnUtility = utility('grid-column', (value) => ({
gridColumn: css`span ${value} / span ${value}`,
}));
const gridSpanMap = {};
for (let i = 1; i <= 12; i++) {
gridSpanMap[i] = i;
}
createGridColumnUtility(gridSpanMap);
export default s;
._grid-column\:1 { grid-column: span 1 / span 1; }
._grid-column\:2 { grid-column: span 2 / span 2; }
._grid-column\:3 { grid-column: span 3 / span 3; }
/* ... */
._grid-column\:12 { grid-column: span 12 / span 12; }
Media Query Modifiers
You can create modifiers for responsive design using media queries:
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),
}, {
modifiers: [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
Modifiers can be combined to create complex utility variations that handle multiple conditions:
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),
}, {
modifiers: [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 utilities focused: Each utility should have a single, clear responsibility and do one thing well.
- Use descriptive naming: The
_property:value
format makes utilities intuitive and CSS-like to use. - Leverage variables: Build utilities on top of your design system variables for consistency and maintainability.
- Create reusable modifiers: Define common modifiers (hover, focus, dark mode) once and reuse them across multiple utilities.
- Plan modifier combinations: Consider which modifiers work well together and design your utilities to handle common combinations.
- Use multi-key modifiers for mutually exclusive states: Breakpoints, themes, and directional variants benefit from multi-key modifier patterns.
- Create composables: Group related utilities and their modifiers into composable functions for better organization and reusability.
- Integrate with variants: Design utilities to work seamlessly with responsive, state, and custom variants.
- Batch utility creation: Use object mapping to create multiple related utilities efficiently.
- Test modifier combinations: Ensure that complex modifier combinations produce the expected CSS output.
- Document modifier behavior: Clear documentation helps team members understand when and how to use specific modifier combinations.
FAQ
Utilities are single-purpose classes focused on individual CSS properties, while components are collections of styles that define entire UI elements. Utilities are the building blocks that components can use.
The _property:value
format is more natural and CSS-like, making it intuitive for developers familiar with CSS. It clearly separates the property from the value while maintaining readability.
When multiple modifiers are applied to a utility, Styleframe generates all possible combinations in alphabetical order. This gives you fine-grained control over exactly when styles should be applied.
Yes! Modifiers are just JavaScript functions that transform CSS declarations. You can create any conditional styling logic you need, from simple state changes to complex media queries.
Single-key modifiers (like hover
) apply the same transformation regardless of the utility value. Multi-key modifiers (like responsive breakpoints) apply different transformations based on the specific key, making them perfect for mutually exclusive conditions.
Yes, you can use JavaScript logic when building the value mappings to create conditional utilities based on configuration or runtime values.
The _property:value
format naturally prevents most conflicts by being explicit about the CSS property being targeted. Modifiers further namespace utilities by prefixing them with the modifier name.
Generate only the utilities and modifiers you need by conditionally building value mappings and modifier arrays based on your design system requirements. Tree-shaking will eliminate unused combinations.
Use modifiers for variations of the same CSS property that depend on external conditions (hover, media queries, themes). Create separate utilities for fundamentally different CSS properties or when the styling logic is completely different.
Start with simple modifier combinations and build up complexity gradually. Use browser developer tools to inspect the generated CSS and verify that the expected styles are being applied in the right conditions.
Interpolation
Styleframe interpolation provides a powerful CSS template literal utility that enables dynamic value insertion with full type safety. Create flexible, maintainable CSS values that adapt to your design system.
Recipes
Styleframe recipes provide powerful component variants with type-safe configuration options. Create flexible, reusable UI components with consistent styling patterns and runtime variant selection.