Composables
Overview
Composables in Styleframe are reusable functions that take a styleframe instance as their first parameter and provide pre-configured variables, selectors, utilities, or other styling patterns. They serve as the building blocks of your design system, enabling you to create consistent, modular, and maintainable styling solutions.
Why use composables?
Composables help you:
- Organize your design system: Group related styling logic into focused, reusable functions.
- Ensure consistency: Define design tokens and patterns once, use them everywhere.
- Improve maintainability: Centralize styling logic in composable functions that can be easily updated and tested.
- Scale your design system: Build complex design systems from simple, composable building blocks.
Defining Composables
Variable Composables
Variable composables are functions that provide reusable design tokens for your design system that:
- Typically follow the
use<Context>Variables
naming pattern (e.g.useColorVariables
). - Take a styleframe instance (
s: Styleframe
) as their first parameter. - Define variables using
default: true
to ensure they are only defined once, even if the composable is called multiple times. - Return the defined variables to be used in selectors or utilities.
import { styleframe } from 'styleframe';
import type { Styleframe } from 'styleframe';
export function useSpacingVariables(s: Styleframe) {
const { variable } = s;
const spacing = variable('spacing', '1rem', { default: true });
const spacingXs = variable('spacing--xs',
css`calc(${ref(spacing)} * 0.25)`, { default: true });
const spacingSm = variable('spacing--sm',
css`calc(${ref(spacing)} * 0.5)`, { default: true });
const spacingMd = variable('spacing--md',
ref(spacing), { default: true });
const spacingLg = variable('spacing--lg',
css`calc(${ref(spacing)} * 1.5)`, { default: true });
const spacingXl = variable('spacing--xl',
css`calc(${ref(spacing)} * 2)`, { default: true });
return {
spacing,
spacingXs,
spacingSm,
spacingMd,
spacingLg,
spacingXl,
};
}
import { styleframe } from 'styleframe';
import { useSpacingVariables } from './composables';
const s = styleframe();
const { spacingMd } = useSpacingVariables(s);
selector('.example', {
padding: ref(spacingMd),
});
export default s;
:root {
--spacing: 1rem;
--spacing--xs: calc(var(--spacing) * 0.25);
--spacing--sm: calc(var(--spacing) * 0.5);
--spacing--md: var(--spacing);
--spacing--lg: calc(var(--spacing) * 1.5);
--spacing--xl: calc(var(--spacing) * 2);
}
.example {
padding: var(--spacing--md);
}
Selector Composables
Selector composables are functions that provide reusable component styles for your design system that:
- Typically follow the
use<Context>Selectors
naming pattern (e.g.useButtonSelectors
,useCardSelectors
). - Take a styleframe instance (
s: Styleframe
) as their first parameter. - Import design tokens from variable composables using their returned references.
import { styleframe } from 'styleframe';
import type { Styleframe } from 'styleframe';
export function useCardSelectors(s: Styleframe) {
const { selector, ref } = s;
const { colorNeutral100, colorNeutral500 } = useColorVariables(s);
const { spacingMd, spacingLg } = useSpacingVariables(s);
const card = selector('.card', {
backgroundColor: ref(colorNeutral100),
borderRadius: '0.5rem',
padding: ref(spacingLg),
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1)',
'.card-title': {
fontSize: '1.25rem',
fontWeight: '600',
marginBottom: ref(spacingMd),
},
'.card-content': {
color: ref(colorNeutral500),
lineHeight: '1.6',
},
});
}
import { styleframe } from 'styleframe';
import { useCardSelectors } from './composables';
const s = styleframe();
useCardSelectors(s);
export default s;
:root {
// ... Variables
}
.card {
background-color: var(--color--neutral-100);
border-radius: 0.5rem;
padding: var(--spacing--lg);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
.card-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: var(--spacing--md);
}
.card-content {
color: var(--color--neutral-500);
line-height: 1.6;
}
}
Utility Composables
Utility composables provide atomic CSS classes for your design system. They typically follow the use<Context>Utilities
naming pattern:
import { styleframe, type Styleframe } from 'styleframe';
export function useSpacingUtilities(s: Styleframe) {
const { utility, ref } = s;
const { spacingXs, spacingSm, spacingMd, spacingLg, spacingXl } = useSpacingVariables(s);
const spacingMap = {
'xs': ref(spacingXs),
'sm': ref(spacingSm),
'md': ref(spacingMd),
'lg': ref(spacingLg),
'xl': ref(spacingXl),
};
const createPaddingUtility = utility('padding', (value) => ({
padding: value,
}));
const createMarginUtility = utility('margin', (value) => ({
margin: value,
}));
createPaddingUtility(spacingMap);
createMarginUtility(spacingMap);
return {
createPaddingUtility,
createMarginUtility,
};
}
import { styleframe } from 'styleframe';
import { useSpacingUtilities } from './composables';
const s = styleframe();
useSpacingUtilities(s);
export default s;
._padding\:xs { padding: var(--spacing--xs); }
._padding\:sm { padding: var(--spacing--sm); }
._padding\:md { padding: var(--spacing--md); }
._padding\:lg { padding: var(--spacing--lg); }
._padding\:xl { padding: var(--spacing--xl); }
._margin\:xs { margin: var(--spacing--xs); }
._margin\:sm { margin: var(--spacing--sm); }
._margin\:md { margin: var(--spacing--md); }
._margin\:lg { margin: var(--spacing--lg); }
._margin\:xl { margin: var(--spacing--xl); }
Recipe Composables
Recipe composables are functions that encapsulate complex styling patterns or component styles. They can combine variables, selectors, and utilities into a cohesive design system component.
- They typically follow the
use<Context>Recipe
naming pattern. - They take a styleframe instance (
s: Styleframe
) as their first parameter.
import { styleframe } from 'styleframe';
import type { Styleframe } from 'styleframe';
export function useButtonRecipe(s: Styleframe) {
const { recipe } = s;
recipe('button', {
borderWidth: 'thin',
borderStyle: 'solid',
}, {
color: {
primary: {
background: 'primary',
color: 'white',
borderColor: 'primary',
},
secondary: {
background: 'secondary',
color: 'white',
borderColor: 'secondary',
}
},
size: {
sm: {
padding: 'sm',
fontSize: 'sm',
},
md: {
padding: 'md',
fontSize: 'md',
},
lg: {
padding: 'lg',
fontSize: 'lg',
}
}
});
}
Theme Composables
Combine themes with composables for organized, reusable theming patterns:
import type { Styleframe } from 'styleframe';
export function useColorVariables(s: Styleframe) {
const colorPrimary = variable('color--primary',
'#4850ec', { default: true });
const colorSecondary = variable('color--secondary',
'#7297f4', { default: true });
return { colorPrimary, colorSecondary };
}
export function useVibrantBrandTheme(s: Styleframe) {
const { theme } = s;
const { colorPrimary, colorSecondary } = useColorVariables(s);
theme('brand-vibrant', (ctx) => {
const { variable } = ctx;
variable(colorPrimary, '#ec4899');
variable(colorSecondary, '#f472b6');
});
}
export function useMinimalBrandTheme(s: Styleframe) {
const { theme } = s;
const { colorPrimary, colorSecondary } = useColorVariables(s);
theme('brand-minimal', (ctx) => {
const { variable } = ctx;
variable(colorPrimary, '#374151');
variable(colorSecondary, '#6b7280');
});
}
import { styleframe } from 'styleframe';
import { useColorVariables, useVibrantBrandTheme, useMinimalBrandTheme } from './composables';
const s = styleframe();
useColorVariables(s);
useVibrantBrandTheme(s);
useMinimalBrandTheme(s);
export default s;
:root {
--color--primary: #4850ec;
--color--secondary: #7297f4;
}
[data-theme="brand-vibrant"] {
--color--primary: #ec4899;
--color--secondary: #f472b6;
}
[data-theme="brand-minimal"] {
--color--primary: #374151;
--color--secondary: #6b7280;
}
Using Composables
To use composables in your project, simply call them with your styleframe instance:
import { styleframe } from 'styleframe';
import { useColorVariables, useSpacingVariables } from './design-system/variables';
import { useButtonSelectors, useCardSelectors } from './design-system/selectors';
import { useSpacingUtilities } from './design-system/utilities';
import { useButtonRecipe } from './design-system/recipes';
const s = styleframe();
// Variable composables
useColorVariables(s);
useSpacingVariables(s);
// Selector composables
useButtonSelectors(s);
useCardSelectors(s);
// Utility composables
useSpacingUtilities(s);
// Recipe composables
useButtonRecipe(s);
export default s;
Examples
Composable Composition
Composables can call other composables to build complex design systems:
import { type Styleframe } from 'styleframe';
export function useDesignSystem(s: Styleframe) {
// Foundation variables
useColorVariables(s);
useSpacingVariables(s);
useTypographyVariables(s);
// Component selectors
useButtonSelectors(s);
useCardSelectors(s);
useFormSelectors(s);
// Utility classes
useSpacingUtilities(s);
useLayoutUtilities(s);
useColorUtilities(s);
}
import { styleframe } from 'styleframe';
import { useDesignSystem } from './composables';
const s = styleframe();
useDesignSystem(s);
export default s;
Configurable Composables
Create composables that accept configuration options:
import { type Styleframe } from 'styleframe';
type DesignSystemColor = 'primary' | 'secondary' | 'accent';
type ColorConfig = Partial<Record<DesignSystemColor, string>>;
export function useColorVariables(s: Styleframe, config: ColorConfig = {}) {
const { variable } = s;
const colorPrimary = variable('color--primary',
config.primary ?? '#006cff', { default: true });
const colorSecondary = variable('color--secondary',
config.secondary ?? '#ff6b6b', { default: true });
const colorAccent = variable('color--accent',
config.accent ?? '#4ecdc4', { default: true });
return { colorPrimary, colorSecondary, colorAccent };
}
import { styleframe } from 'styleframe';
import { useColorVariables } from './composables';
const s = styleframe();
useColorVariables(s, {
primary: '#ff4081',
secondary: '#3f51b5',
});
export default s;
Best Practices
- Use the
default
option for variables in composables to prevent accidental overwrites when the composable is called multiple times. - Follow naming conventions: Use
use<Context>Variables
,use<Context>Selectors
,use<Context>Utilities
patterns. - Return references from variable composables so other composables can reuse them.
- Keep composables focused on a single responsibility (colors, spacing, buttons, etc.).
- Document your composables with clear descriptions of what they provide and any dependencies.
FAQ
The default: true
option ensures that if a variable is already defined, it won't be overridden. This is crucial for composables that might be called multiple times or in different orders.
While composables using default: true
are generally safe to call in any order, it's best practice to call variable composables before selector/utility composables that depend on them.
Return variable references from your variable composables and import them in other composables. You can also call variable composables within other composables.
Yes! You can use JavaScript logic within composables to conditionally create variables, selectors, or utilities based on configuration or runtime conditions.
Consider organizing by type (variables, components, utilities) or by feature area (forms, navigation, typography). Use a consistent folder structure and naming convention.
Absolutely! Create different composables for different themes and conditionally apply them based on your theming system.
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.
Fluid Typography
Styleframe variables are the foundation of your design system. They let you define design tokens such as colors, spacing, typography, and more.