@noema/motif
A type-safe styling library for React components built on top of Vanilla Extract. @noema/motif
allows you to create styled components with consistent theme-aware props that cleanly separate styling from component logic.
Installation
@vanilla-extract/css
and @vanilla-extract/sprinkles
will also need to get installed.
npm install @noema/motif @vanilla-extract/css @vanilla-extract/sprinkles
Basic Usage
1. Define your styles (in a .css.ts
file)
// styles.css.ts
import { createSprinkles, defineProperties } from '@vanilla-extract/sprinkles';
import { motifStyle } from '@noema/motif';
// Define your style properties
export const styles = motifStyle(
[
'width',
'height',
'fontSize',
// Add more CSS properties as needed
],
{
shorthands: {
// Define shorthands if needed
size: ['width', 'height'],
},
},
);
// Define your sprinkles (theme-based properties)
const colorProperties = defineProperties({
properties: {
color: {
primary: 'blue',
secondary: 'purple',
// Add more theme colors
},
backgroundColor: {
primary: 'white',
secondary: 'lightgray',
// Add more theme colors
},
},
});
export const sprinkles = createSprinkles(colorProperties);
2. Create your components
// components.tsx
import { createMotif, motif } from '@noema/motif';
import { sprinkles, styles } from './styles.css';
// Creae a single component
const BoxComp = motif('div', sprinkles, styles);
// Or multiple components
const elements = ['div', 'button', 'span', 'h1'] as const;
// Create your styled components
export const Styled = createMotif(elements, sprinkles, styles);
// Export individual components for convenience
export const { div: Box, button: Button } = Styled;
3. Use your components
import { Box, Button } from './components';
function MyComponent() {
return (
<Box width={200} height={100} color="primary">
<Button fontSize={16} backgroundColor="secondary">
Click me
</Button>
</Box>
);
}
API Reference
motifStyle(properties, options)
Creates a style configuration for use with motif
or createMotif
.
Parameters:
properties
: Array of CSS property names to be controlled via propsoptions
: Configuration optionsprefix
: Prefix for CSS variable names (default: 'motif')base
: Base styles to apply to all componentsshorthands
: Record of shorthand property names mapping to CSS properties
Returns: Style configuration object
createMotif(elements, sprinkles, config)
Creates a collection of styled components.
Parameters:
elements
: Array of HTML element types to create components forsprinkles
: Vanilla Extract sprinkles functionconfig
: Style configuration created withmotifStyle
Returns: Object with styled components for each element
motif(element, sprinkles, config)
Core function that creates a single styled component.
Parameters:
element
: HTML element typesprinkles
: Vanilla Extract sprinkles functionconfig
: Style configuration created withmotifStyle
Returns: Styled React component
How It Works
Motif combines two styling approaches to create a powerful and flexible styling system:
Core Concepts
motifStyle
- Defines which CSS properties can be used as component props- Creates CSS variables for each property
- Generates CSS classes that apply these variables
- Allows defining shorthand props (like
p
for all paddings)
Runtime Styling Flow
- When you pass props to a motif component (e.g.,
width={200}
):- The value is converted to a CSS variable value
- The corresponding CSS class is applied to the component
- The CSS variable is set as an inline style
- When you pass props to a motif component (e.g.,
This hybrid approach enables:
- Type safety through TypeScript
- Performance of atomic CSS
- Flexibility of runtime values via CSS variables
- Theme consistency via Vanilla Extract's design system
Example
// primitives.css.ts
import { createSprinkles, defineProperties } from '@vanilla-extract/sprinkles';
import { motifStyle } from './motif-style.js';
export const styles = motifStyle(
[
'width',
'height',
'minHeight',
'minWidth',
'maxWidth',
'maxHeight',
'gridTemplateColumns',
'gridTemplateRows',
'gridTemplateAreas',
'gridColumn',
'gridRow',
'gridArea',
'top',
'bottom',
'left',
'right',
'zIndex',
'fontSize',
],
{ shorthands: {} },
);
// Structural properties for layout
const structureProperties = defineProperties({
properties: {
display: ['none', 'flex', 'block', 'inline', 'grid'],
flexDirection: ['row', 'column'],
// ... more properties
},
});
// Box style with padding shorthands
export const boxStyle = motifStyle(
['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
{
shorthands: {
py: ['paddingTop', 'paddingBottom'],
px: ['paddingLeft', 'paddingRight'],
p: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
},
},
);
export const sprinkles = createSprinkles(structureProperties);
// primitives.tsx
import { MotifComponents, createMotif } from './create-motif.js';
import { sprinkles, styles } from './primitives.css.js';
const NODES = [
'button',
'div',
'h1',
'h2',
'h3',
'span',
'table',
'thead',
'tbody',
'th',
'tr',
'td',
] as const;
// Create primitive components
export const Primitive = createMotif(NODES, sprinkles, styles);
export type PrimitiveProps = MotifComponents<typeof Primitive>;
// Export common components
export const { button: Button, div: Box } = Primitive;
Additional Utilities
The library also includes additional utilities:
createContext
- A utility for creating React context with improved type safety and error messagescomposeRender
- A utility for composing render props with improved type safety
License
MIT