Atomic CSS Utilities
Type-safe atomic CSS utilities with @typestyles/props
Atomic CSS Utilities
The @typestyles/props package provides a type-safe way to generate atomic CSS utility classes, similar to Tailwind CSS but with full TypeScript inference and zero runtime overhead.
Installation
npm install @typestyles/props
Quick Start
import { defineProperties, createProps } from '@typestyles/props';
// Define your atomic utilities
const atoms = createProps(
'atom',
defineProperties({
properties: {
display: ['flex', 'block', 'grid', 'none'],
padding: { 0: '0', 1: '4px', 2: '8px', 3: '16px' },
gap: { 0: '0', 1: '4px', 2: '8px', 3: '16px' },
},
})
);
// Use them with full type safety
atoms({
display: 'flex',
padding: 2,
gap: 1,
});
// Returns: "atom-display-flex atom-padding-2 atom-gap-1"
Defining Properties
Use defineProperties() to define a collection of CSS properties with their allowed values:
Array-Based Values
For enumerated values like display modes:
const utilities = defineProperties({
properties: {
display: ['flex', 'block', 'grid', 'none', 'inline-flex'],
flexDirection: ['row', 'column', 'row-reverse', 'column-reverse'],
alignItems: ['start', 'center', 'end', 'stretch', 'baseline'],
},
});
Object-Based Values
For scale-based values like spacing or font sizes:
const utilities = defineProperties({
properties: {
padding: {
0: '0',
1: '0.25rem',
2: '0.5rem',
3: '0.75rem',
4: '1rem',
8: '2rem',
},
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
},
},
});
Responsive Conditions
Add breakpoints and other conditional styles:
const responsive = defineProperties({
conditions: {
mobile: { '@media': '(min-width: 640px)' },
tablet: { '@media': '(min-width: 768px)' },
desktop: { '@media': '(min-width: 1024px)' },
},
properties: {
display: ['flex', 'block', 'grid'],
flexDirection: ['row', 'column'],
},
});
const atoms = createProps('atom', responsive);
// Apply responsive values
atoms({
display: 'block', // Base value
flexDirection: { mobile: 'column', desktop: 'row' }, // Responsive
});
// Returns: "atom-display-block atom-flexDirection-mobile-column atom-flexDirection-desktop-row"
Condition Types
Media Queries:
{ mobile: { '@media': '(min-width: 768px)' } }
Container Queries:
{ wide: { '@container': '(min-width: 400px)' } }
Feature Queries:
{ supportsGrid: { '@supports': '(display: grid)' } }
Custom Selectors:
{ hover: { selector: '&:hover' } }
{ dark: { selector: '[data-theme="dark"] &' } }
Shorthands
Define shorthands that expand to multiple properties:
const spacing = defineProperties({
properties: {
paddingTop: { 0: '0', 1: '4px', 2: '8px' },
paddingRight: { 0: '0', 1: '4px', 2: '8px' },
paddingBottom: { 0: '0', 1: '4px', 2: '8px' },
paddingLeft: { 0: '0', 1: '4px', 2: '8px' },
},
shorthands: {
padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
paddingX: ['paddingLeft', 'paddingRight'],
paddingY: ['paddingTop', 'paddingBottom'],
},
});
const atoms = createProps('atom', spacing);
atoms({ padding: 2 });
// Expands to: paddingTop, paddingRight, paddingBottom, paddingLeft all set to 2
// Returns: "atom-paddingTop-2 atom-paddingRight-2 atom-paddingBottom-2 atom-paddingLeft-2"
atoms({ paddingX: 1, paddingY: 2 });
// Returns: "atom-paddingLeft-1 atom-paddingRight-1 atom-paddingTop-2 atom-paddingBottom-2"
Combining Property Collections
Merge multiple property collections into one props function:
const layout = defineProperties({
properties: {
display: ['flex', 'block', 'grid'],
position: ['relative', 'absolute', 'fixed', 'sticky'],
},
});
const spacing = defineProperties({
properties: {
padding: { 0: '0', 1: '4px', 2: '8px' },
margin: { 0: '0', 1: '4px', 2: '8px', auto: 'auto' },
},
});
const colors = defineProperties({
properties: {
color: { primary: '#0066ff', secondary: '#6b7280' },
backgroundColor: { white: '#ffffff', gray: '#f3f4f6' },
},
});
// Combine all collections
const atoms = createProps('atom', layout, spacing, colors);
// Use properties from any collection
atoms({
display: 'flex',
padding: 2,
color: 'primary',
});
Default Conditions
Set a default condition for all property values:
const mobileFirst = defineProperties({
conditions: {
tablet: { '@media': '(min-width: 768px)' },
desktop: { '@media': '(min-width: 1024px)' },
},
defaultCondition: false, // No condition by default (mobile-first)
properties: {
fontSize: { sm: '14px', base: '16px', lg: '18px' },
},
});
const desktopFirst = defineProperties({
conditions: {
mobile: { '@media': '(max-width: 768px)' },
},
defaultCondition: 'mobile', // All values get mobile condition by default
properties: {
fontSize: { sm: '14px', base: '16px', lg: '18px' },
},
});
SSR Support
CSS is automatically injected and available for SSR:
import { getRegisteredCss } from 'typestyles';
// Define and create your atoms
const atoms = createProps('atom', defineProperties({ /* ... */ }));
// CSS is automatically registered
const css = getRegisteredCss();
// Contains all atomic utility classes
Composing with Component Styles
Combine atomic utilities with component-specific styles using styles.compose():
import { styles } from 'typestyles';
import { createProps, defineProperties } from '@typestyles/props';
const atoms = createProps(
'atom',
defineProperties({
properties: {
display: ['flex', 'grid'],
gap: { 0: '0', 1: '4px', 2: '8px' },
padding: { 0: '0', 1: '4px', 2: '8px', 3: '16px' },
},
})
);
const card = styles.create('card', {
base: {
borderRadius: '8px',
border: '1px solid #e5e5e5',
background: 'white',
},
});
// Compose together
const flexCard = styles.compose(
card,
atoms({ display: 'flex', gap: 2, padding: 3 })
);
flexCard('base');
// "card-base atom-display-flex atom-gap-2 atom-padding-3"
Real-World Example
Building a complete design system:
import { defineProperties, createProps } from '@typestyles/props';
// Define responsive breakpoints and utilities
const atoms = createProps(
'ds',
defineProperties({
conditions: {
sm: { '@media': '(min-width: 640px)' },
md: { '@media': '(min-width: 768px)' },
lg: { '@media': '(min-width: 1024px)' },
xl: { '@media': '(min-width: 1280px)' },
dark: { selector: '[data-theme="dark"] &' },
hover: { selector: '&:hover' },
},
properties: {
// Layout
display: ['none', 'flex', 'block', 'grid', 'inline-flex'],
flexDirection: ['row', 'column'],
alignItems: ['start', 'center', 'end', 'stretch'],
justifyContent: ['start', 'center', 'end', 'between', 'around'],
// Spacing
gap: { 0: '0', 1: '0.25rem', 2: '0.5rem', 3: '0.75rem', 4: '1rem' },
padding: { 0: '0', 1: '0.25rem', 2: '0.5rem', 4: '1rem', 8: '2rem' },
margin: { 0: '0', auto: 'auto', 1: '0.25rem', 2: '0.5rem' },
// Typography
fontSize: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem' },
fontWeight: { normal: '400', medium: '500', semibold: '600', bold: '700' },
// Colors
color: { primary: '#3b82f6', secondary: '#64748b', muted: '#94a3b8' },
backgroundColor: { white: '#fff', gray: '#f1f5f9', transparent: 'transparent' },
},
shorthands: {
p: ['padding'],
px: ['paddingLeft', 'paddingRight'],
py: ['paddingTop', 'paddingBottom'],
m: ['margin'],
},
})
);
// Use in components
export function Button() {
return (
<button
className={atoms({
display: 'inline-flex',
alignItems: 'center',
gap: 2,
px: 4,
py: 2,
fontSize: { sm: 'sm', md: 'base' },
fontWeight: 'semibold',
color: 'white',
backgroundColor: { _: 'primary', hover: 'secondary', dark: 'gray' },
})}
>
Click me
</button>
);
}
Class Naming
Classes follow a predictable pattern:
{namespace}-{property}-{value}
{namespace}-{property}-{condition}-{value}
Examples:
atom-display-flexatom-display-mobile-blockatom-padding-2atom-padding-desktop-4
TypeScript
Full type inference and autocomplete for:
- Property names
- Property values
- Condition names
- Shorthand names
const atoms = createProps('atom', defineProperties({
properties: {
display: ['flex', 'block'],
},
}));
atoms({ display: 'flex' }); // ✅
atoms({ display: 'grid' }); // ❌ Type error: 'grid' not in ['flex', 'block']
atoms({ fontSize: '16px' }); // ❌ Type error: 'fontSize' not defined
Inspecting Properties
Check which properties are available:
const atoms = createProps('atom', defineProperties({
properties: { display: ['flex', 'block'] },
shorthands: { d: ['display'] },
}));
atoms.properties.has('display'); // true
atoms.properties.has('d'); // true (shorthand)
atoms.properties.has('fontSize'); // false
Performance
- Zero runtime overhead - All CSS is pre-generated at module load time
- Automatic deduplication - Classes are only generated once
- Small bundle size - Only the props function and a lookup map are included
- Fast runtime - Just string concatenation, no CSS generation at runtime