Slicknode Stylemapper
Easily create styled, strictly typed React components and simplify your component code.
Stylemapper is a small, flexible and zero-dependency utility to add CSS classes to React components. It eliminates the boilerplate you usually need for changing styles based on state, define typescript definitions, etc. This simplifies the creation and maintenance of your style and design system:
- Get strictly typed components without writing Typescript prop type definitions (Stylemapper infers types automatically)
- Automatically create variant props without complicating your component code (success/error, large/medium etc.)
- Add styles to 3rd party libraries without manually creating wrapper components, type definitions, etc.
- Have a single source of truth for your styles instead of spreading classnames all over your React components
Works especially great with utility based CSS frameworks like Tailwind CSS.
Installation
Add Slicknode Stylemapper as a dependency to your project:
npm install --save @slicknode/stylemapper
Usage
Import the styled
utility function and create styled components. Examples are using Tailwind CSS utility classes.
Basic Example
import { styled } from '@slicknode/stylemapper';
// Create styled components with CSS classes
const Menu = styled('ul', 'space-x-2 flex');
const MenuItem = styled('li', 'w-9 h-9 flex items-center justify-center');
// Then use the components in your app
const App = () => {
return (
<Menu>
<MenuItem>Home</MenuItem>
<MenuItem>Product</MenuItem>
<MenuItem>Signup Now</MenuItem>
</Menu>
);
};
Variants
Create variants by passing a configuration object. Stylemapper automatically infers the correct prop type definitions and passes the resulting className
prop to the component:
const Button = styled('button', {
variants: {
intent: {
neutral: 'bg-slate-300 border border-slate-500',
danger: 'bg-red-300 border border-red-500',
success: 'bg-green-300 border border-green-500',
},
size: {
small: 'p-2',
medium: 'p-4',
large: 'p-8',
},
// Add any number of variants...
},
// Optionally set default variant values
defaultVariants: {
intent: 'neutral',
size: 'medium',
},
});
const App = () => {
return (
<Button intent={'danger'} size={'large'}>
Delete Account
</Button>
);
};
Compound Variants
If you only want to add class names to a component if multiple props have particular values, you can configure compountVariants
:
const Button = styled('button', {
variants: {
intent: {
danger: 'bg-red-300',
success: 'bg-green-300',
},
outline: {
true: 'border',
false: '',
},
// Add any number of variants...
},
compoundVariants: [
{
intent: 'success',
outline: true,
className: 'border-green-500',
},
{
intent: 'danger',
outline: true,
className: 'border-red-500',
},
],
});
const App = () => {
return (
<Button intent={'danger'} outline>
Delete Account
</Button>
);
};
Custom Components
Stylemapper works with any React component, as long as the component has a className
prop. This makes it easy to add styles to your own components or to UI libraries like Headless UI, Radix UI and Reach UI. Just pass in the component as a first argument:
const CustomComponent = ({ className }) => {
return (
// Make sure you add the className from the props to the DOM node
<div className={className}>My custom react component</div>
);
};
const StyledCustomComponent = styled(CustomComponent, {
variants: {
intent: {
danger: 'bg-red-300 border border-red-500',
success: 'bg-green-300 border border-green-500',
},
},
});
// Extending styled components
const SizedComponent = styled(StyledCustomComponent, {
variants: {
size: {
small: 'p-2',
medium: 'p-4',
large: 'p-8',
},
},
});
const App = () => {
return (
<SizedComponent intent="danger" size="large">
Large error message
</SizedComponent>
);
};
Variant Type Casting
You can define boolean
and numeric
variant values. The type definition for the resulting prop is automatically inferred:
const StyledComponent = styled('div', {
variants: {
selected: {
true: 'bg-red-300 border border-red-500',
false: 'bg-green-300 border border-green-500',
},
size: {
1: 'p-2'
2: 'p-4'
3: 'p-8'
}
},
});
const App = () => (
// This component now expects a boolean and a number value as props
<StyledComponent selected={true} size={2}/>
);
Prop Forwarding
By default, variant props are not passed to the wrapped component to prevent invalid props to be attached to DOM nodes. If you need the values of variants inside of your custom components, specify them in the configuration:
const CustomComponent = ({ open, className }) => {
return (
<div className={className}>Component is {open ? 'open' : 'closed'}</div>
);
};
const StyledComponent = styled('div', {
variants: {
selected: {
true: 'bg-red-300 border border-red-500',
false: 'bg-green-300 border border-green-500',
},
},
forwardProps: ['selected'],
});
Composing Configurations
You can pass any number of configurations to the styled
function. This allows you to reuse styles and variants across components. Pass either a string with class names or a configuration object as input values:
import { intentVariants, sizeVariants } from './shared';
const StyledComponent = styled(
'div',
// Add some base styles that are added every time
'p-2 flex gap-2',
// Add imported variants
intentVariants,
sizeVariants,
// Add other custom variants
{
selected: {
true: 'border border-green-500',
false: 'border border-green-100',
},
}
);
IntelliSense for Tailwind CSS
If you are using the offical TailwindCSS extension for VSCode, you can enable intellisense for style mapper by updating your settings:
{
"tailwindCSS.experimental.classRegex": [
["styled\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
]
}
Credits
This library is heavily inspired by Stitches, a great CSS in Javascript library. Stylemapper brings a similar API to utility based CSS frameworks without requiring a specific library.