Skip to content

Instantly share code, notes, and snippets.

@jckw
Last active February 23, 2023 18:42
Show Gist options
  • Save jckw/1353514da357ba265958e3cbba102e07 to your computer and use it in GitHub Desktop.
Save jckw/1353514da357ba265958e3cbba102e07 to your computer and use it in GitHub Desktop.
Stitches-inspired variant helper function for Tailwind. Complete with TypeScript autocomplete for variant fields.
type Classnames = string[] | string
type VariantConfig = {
[variant: string]: { [option: string]: Classnames }
}
type Config<V extends VariantConfig> = {
base: Classnames
variants: V
compoundVariants?: ({
[key in keyof V]?: keyof V[key]
} & { css: Classnames })[]
defaultVariants?: {
[key in keyof V]?: keyof V[key]
}
}
// A tool for flattening arrays, could use lodash's flattenDeep
const makeArray = (x: string[] | string) => (Array.isArray(x) ? x : [x])
const variants =
<V extends VariantConfig>(config: Config<V>) =>
(variantProps: {
[key in keyof V]?: keyof V[key]
}) => {
const variantsActive = {
...config.defaultVariants,
...variantProps,
}
const simpleStyles = Object.entries(variantsActive).map(([name, value]) =>
makeArray(config.variants[name as keyof V][value as string]).join(' ')
)
const compoundStyles = config.compoundVariants?.map((compoundVariant) => {
const { css, ...vMatch } = compoundVariant
const match = Object.entries(vMatch).every(
([k, v]) => (variantsActive as { [key: string]: string })[k] === v
)
return makeArray(match ? css : '').join(' ')
})
return [
...makeArray(config.base),
...makeArray(simpleStyles),
...(compoundStyles || []),
].join(' ')
}
export default variants
@jckw
Copy link
Author

jckw commented Jan 13, 2022

Example usage

const btn = variants({
  base: ['px-2 py-1'],
  variants: {
    style: {
      outline: 'border rounded',
      solid: [
        // use arrays or strings
        // useful for breaking up modifier groups
        'bg-blue-500',
        'hover:bg-blue-700 hover:scale-105'
      ],
    },
    size: {
      small: 'text-sm',
      large: 'text-xl',
    },
  },
  compoundVariants: [
    {
      style: 'outline',
      size: 'large',
      css: 'text-pink-500',
    },
  ],
  defaultVariants: {
    size: 'large',
  },
})


const MyComponent = () => {
  return <button className={btn({ style: 'outline' })}>Click me</button>
}

// will have the class 'px-2 py-1 border rounded text-xl text-pink'

// comes with TypeScript autocompletion whereever you need it :)

@paraform
Copy link

@tailwindlabs needs to implement something like this - it's awesome!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment