Spacing
Overview
The spacing composables help you create consistent spacing systems with minimal code. They generate spacing variables that can be easily referenced throughout your application, enabling flexible theming and consistent spatial relationships between elements.
Why use spacing composables?
Spacing composables help you:
- Centralize spacing values: Define all your spacing units in one place for easy management.
- Enable flexible theming: Override spacing values to instantly update layouts across your application.
- Maintain consistency: Use semantic names to ensure consistent spacing usage throughout your design system.
- Create harmonious scales: Integrate with modular scales to generate mathematically proportional spacing systems.
useSpacing
The useSpacing()
function creates a set of spacing variables from a simple object of spacing value definitions.
import { styleframe } from 'styleframe';
import { useSpacing } from '@styleframe/theme';
const s = styleframe();
const {
spacing,
spacingXs,
spacingSm,
spacingMd,
spacingLg,
spacingXl,
} = useSpacing(s, {
default: '1rem',
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem',
} as const);
export default s;
:root {
--spacing: 1rem;
--spacing--xs: 0.25rem;
--spacing--sm: 0.5rem;
--spacing--md: 1rem;
--spacing--lg: 1.5rem;
--spacing--xl: 2rem;
}
Each key in the object becomes a spacing variable with the prefix spacing
, and the export name is automatically converted to camelCase (e.g., default
→ spacing
, xs
→ spacingXs
, md
→ spacingMd
).
default
key for your base spacing unit. It will create a variable named --spacing
without any suffix, making it the natural choice for standard spacing throughout your application.useMultiplier
Integration with
The real power of useSpacing
comes when combined with useMultiplier()
and modular scales. This allows you to create mathematically harmonious spacing systems that maintain consistent proportions.
Create a spacing scale based on a modular scale ratio:
import { styleframe } from 'styleframe';
import { useScale, useScalePowers, useMultiplier, useSpacing } from '@styleframe/theme';
const s = styleframe();
// Use the Major Second scale (1.125) for subtle spacing differences
const { scale } = useScale(s);
// Define base spacing unit
const { spacing } = useSpacing(s, { default: '1rem' });
// Create spacing scale powers
const scalePowers = useScalePowers(s, scale);
// Generate spacing variables automatically
const {
spacingXs,
spacingSm,
spacingMd,
spacingLg,
spacingXl,
spacing2xl,
} = useMultiplier(s, spacing, {
xs: scalePowers[-2], // ~0.79rem
sm: scalePowers[-1], // ~0.89rem
md: scalePowers[0], // 1rem (base)
lg: scalePowers[1], // ~1.13rem
xl: scalePowers[2], // ~1.27rem
'2xl': scalePowers[3], // ~1.42rem
});
export default s;
:root {
--scale--minor-third: 1.2;
--scale: var(--scale--minor-third);
--spacing: 1rem;
--spacing--xs: calc(var(--spacing) * 1 / var(--scale) / var(--scale));
--spacing--sm: calc(var(--spacing) * 1 / var(--scale));
--spacing--md: calc(var(--spacing) * 1);
--spacing--lg: calc(var(--spacing) * var(--scale));
--spacing--xl: calc(var(--spacing) * var(--scale) * var(--scale));
--spacing--2xl: calc(var(--spacing) * var(--scale) * var(--scale) * var(--scale));
}
The useMultiplier()
function multiplies your base spacing by the scale powers, creating a harmonious progression of spacing values. This ensures consistent proportional relationships throughout your design system.
Read more about design scales and take advantage of the flexibility they offer.
useMultiplier()
with scales means you can change your entire spacing system's proportions by simply adjusting the scale ratio. Try different scales like Perfect Fourth (1.333) for more dramatic spacing differences or Major Second (1.125) for subtle variations.Using Spacing Variables
Once created, spacing variables can be used anywhere in your styles:
import { styleframe } from 'styleframe';
import { useSpacing } from '@styleframe/theme';
const s = styleframe();
const { ref, selector, css } = s;
const { spacing, spacingSm, spacingLg } = useSpacing(s, {
default: '1rem',
sm: '0.5rem',
lg: '1.5rem',
} as const);
selector('.card', {
padding: ref(spacing),
marginBottom: ref(spacingLg),
});
selector('.button', {
padding: css`${ref(spacingSm)} ${ref(spacing)}`,
});
selector('.container', {
gap: ref(spacing),
});
export default s;
:root {
--spacing: 1rem;
--spacing--sm: 0.5rem;
--spacing--lg: 1.5rem;
}
.card {
padding: var(--spacing);
margin-bottom: var(--spacing--lg);
}
.button {
padding: var(--spacing--sm) var(--spacing);
}
.container {
gap: var(--spacing);
}
Examples
Semantic Spacing Names
Use semantic names to make spacing intent clear:
import { styleframe } from 'styleframe';
import { useSpacing } from '@styleframe/theme';
const s = styleframe();
const {
spacing,
spacingTight,
spacingComfortable,
spacingLoose,
} = useSpacing(s, {
default: '1rem',
tight: '0.5rem',
comfortable: '1.5rem',
loose: '2rem',
} as const);
export default s;
:root {
--spacing: 1rem;
--spacing--tight: 0.5rem;
--spacing--comfortable: 1.5rem;
--spacing--loose: 2rem;
}
Complete Spacing System
Here's a comprehensive example showing a full spacing system with multiple scales:
import { styleframe } from 'styleframe';
import { useScale, useScalePowers, useMultiplier, useSpacing } from '@styleframe/theme';
const s = styleframe();
// Use Minor Third scale (1.2) for balanced spacing
const { scale } = useScale(s, 'minor-third');
// Define base spacing units
const { spacing, spacingGutter, spacingSection } = useSpacing(s, {
default: '1rem', // Base unit for general spacing
gutter: '1.5rem', // For grid gutters
section: '4rem', // For section spacing
} as const);
// Create scale powers for the base spacing
const scalePowers = useScalePowers(s, scale, [-3, -2, -1, 0, 1, 2, 3, 4]);
// Generate base spacing scale
const {
spacing3xs,
spacing2xs,
spacingXs,
spacingSm,
spacingMd,
spacingLg,
spacingXl,
spacing2xl,
} = useMultiplier(s, spacing, {
'3xs': scalePowers[-3], // ~0.58rem
'2xs': scalePowers[-2], // ~0.69rem
xs: scalePowers[-1], // ~0.83rem
sm: scalePowers[0], // 1rem (base)
md: scalePowers[1], // ~1.2rem
lg: scalePowers[2], // ~1.44rem
xl: scalePowers[3], // ~1.73rem
'2xl': scalePowers[4], // ~2.07rem
});
export default s;
:root {
--scale--minor-third: 1.2;
--scale: var(--scale--minor-third);
--spacing: 1rem;
--spacing--gutter: 1.5rem;
--spacing--section: 4rem;
--spacing--3xs: calc(var(--spacing) * var(--scale-power---3));
--spacing--2xs: calc(var(--spacing) * var(--scale-power---2));
--spacing--xs: calc(var(--spacing) * var(--scale-power---1));
--spacing--sm: calc(var(--spacing) * var(--scale-power--0));
--spacing--md: calc(var(--spacing) * var(--scale-power--1));
--spacing--lg: calc(var(--spacing) * var(--scale-power--2));
--spacing--xl: calc(var(--spacing) * var(--scale-power--3));
--spacing--2xl: calc(var(--spacing) * var(--scale-power--4));
}
Best Practices
- Start with a base unit: Use
1rem
(16px) as your base spacing unit and build everything else from it. - Use the
default
key: This creates a clean--spacing
variable that's perfect for general-purpose spacing. - Integrate with scales: Combine
useSpacing()
withuseMultiplier()
for mathematically harmonious spacing systems. - Limit your scale: Too many spacing options can lead to inconsistency. Aim for 5-8 spacing values.
- Consider both directions: Think about vertical rhythm (margins, padding-block) and horizontal spacing (gaps, padding-inline).
- Test with real content: Ensure your spacing works across different component sizes and content densities.
as const
to ensure the object is treated as a constant type. This helps TypeScript infer the return type of the composables and provides better type safety and autocomplete support.FAQ
card--padding
) rather than expanding your core scale.rem
for most spacing to respect user font size preferences and maintain consistent proportions. Use em
for component-internal spacing that should scale with the component's font size. Avoid px
unless you need pixel-perfect precision that shouldn't scale.You have several options:
- Use
clamp()
for fluid spacing that automatically adjusts, - Override spacing variables at different breakpoints, or
- Use viewport units like
vw
in your calculations.
The clamp()
approach is often the most elegant.
'negative': '-0.5rem'
) or by wrapping a spacing reference in calc()
(e.g., calc(${s.ref(spacing)} * -1)
).useSpacing()
creates spacing variables from explicit values you provide. useMultiplier()
automatically generates spacing variables by multiplying a base spacing variable by scale powers. Use useSpacing()
for manual control or useMultiplier()
for systematic, scale-based spacing.