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>Variablesnaming pattern (e.g.useColorVariables). - Take a styleframe instance (
s: Styleframe) as their first parameter. - Define variables using
default: trueto 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>Selectorsnaming 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>Recipenaming 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
defaultoption 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>Utilitiespatterns. - 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
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.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.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.
Merging
Combine multiple Styleframe instances into a single unified configuration. Perfect for composing design systems, sharing configurations across projects, or building modular styling architectures.