Options
All
  • Public
  • Public/Protected
  • All
Menu

As a component author, one often wants to re-use Tailwind directive styles for defining a component and allow users of the component to override styles using Tailwind rules. The created component can be used as a base for child components and override or add some styles using Tailwind rules.

apply generates one style object, e.g., one CSS class, combining all Tailwind rules by deep merging rules in order of declaration.

import { apply } from 'twind'

const btn = apply`inline-block bg-gray-500 text-base`
// => generates on CSS class with all declarations of the above rules when used

const btnBlock = apply`${btn} block`
// => generates on CSS class with all declarations of btn & block

<button class={tw`${btn}`}>gray-500</button>
// => tw-XXXXX

<button class={tw`${btn} bg-red-500 text-lg`}>red-500 large</button>
// => tw-XXXX bg-red-500 text-lg

<button class={tw`${btnBlock}`}>block button</button>
// => tw-YYYY

💡 Another way to extract common component styles is by using an alias plugin.

The component styles are added before the utility classes to the stylesheet which allows utilities to override component styles.

Why can I not use tw for components?
const Button = ({ className, children}) => {
  return <button className={tw`inline-block bg-gray-500 text-base ${className}`}>{children}</button>
}

const ButtonBlock = ({ className, children}) => {
  return <Button className={`block ${className}`}>{children}</Button>
}

<Button>gray-500</Button>
<Button className="bg-red-500 text-lg">red-500 large</Button>

The example above does not reliably work because the injected CSS classes have all the same specificity and therefore the order in which they appear in the stylesheet determines which styles are applied.

It is really difficult to know which directive does override another. For now, let's stick with bg-* but there are others. The bg prefix and its plugin handle several CSS properties where background-color is only one of them.

This ambiguity makes class based composition really difficult. That was the reason we introduced the override variant.

Consider the following example:

const Button = tw`
  text(base blue-600)
  rounded-sm
  border(& solid 2 blue-600)
  m-4 py-1 px-4
`

// Create a child component overriding some colors
const PurpleButton = tw`
  ${Button}
  override:(text-purple-600 border-purple-600)
`

As you see it is difficult to override certain utility classes on usage or when creating a child component. For this to work Twind introduced the override variant which increases the specificity of the classes it is applied to. But what do you do for a grandchild component or if you want to override the PurpleButton styles? override:override:...? This is where apply should be used.

Tailwind has a component concept using @apply which basically merges the CSS rules of several Tailwind classes into one class. twin.macro does the same.

Details of Tailwind @apply

Tailwind CSS provides @apply to extract component classes which merges the underlying styles of the utility classes into a single CSS class.

.btn-indigo {
  @apply py-2 px-4 bg-indigo-500 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-75;
}

twin.macro does the same during build time to generate CSS-in-JS objects which are evaluated with a runtime like Emotion or styled-components:

const hoverStyles = css`
  &:hover {
    border-color: black;
    ${tw`text-black`}
  }
`
const Input = ({ hasHover }) => <input css={[tw`border`, hasHover && hoverStyles]} />

The tw function from twin.macro acts like the @apply helper from Tailwind CSS.

API

apply accepts the same arguments as tw.

import { tw, apply } from 'twind'

const btn = apply`
  py-2 px-4
  font-semibold
  rounded-lg shadow-md
  focus:(outline-none ring(2 indigo-400 opacity-75))
`

tw`${btn} font-bold`
// => .tw-btn .font-bold
// CSS:
// .tw-XXXX { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; padding-right: 1rem; font-weight: 600; ...}
// .font-bold { font-weight: 700; }

const btnLarge = apply`${btn} py-4 px-8`
// Result: () => ({ paddingTop: '1rem', paddingBottom: '1rem', paddingLeft: '2rem', paddingRight: '2rem', fontWeight: '600', ... })

tw`${btnLarge} rounded-md`
// => .tw-btn-large .rounded-md
// CSS:
// .tw-btn-large { padding-top: 1rem; padding-bottom: 1rem; padding-left: 2rem; padding-right: 2rem; font-weight: 600; ... }
// .rounded-md { ... }

You can use this helper to use apply together with tw:

import { tw, apply } from 'twind'

// There is a better name out there somewhere
const twind = (...args) => tw(apply(...args))

<button className={twind`bg-red bg-blue`}>blue</button>
// => tw-red-blue

document.body.className = twind`bg-blue bg-red`
// => tw-blue-red

Examples

Using apply within preflight

Use Tailwind rules within preflight.

setup({
  preflight: {
    body: apply('bg-gray-900 text-white'),
  },
})
CSS can be used within apply

twind/css can be used to define additional styles.

const btn = apply`
  py-2 px-4
  ${css({
    borderColor: 'black',
  })}
`
Using within CSS

apply can be used with css:

const prose = css(
  apply`text-gray-700 dark:text-gray-300`,
  {
    p: apply`my-5`,
    h1: apply`text-black dark:text-white`,
  },
  {
    h1: {
      fontWeight: '800',
      fontSize: '2.25em',
      marginTop: '0',
      marginBottom: '0.8888889em',
      lineHeight: '1.1111111',
    },
  },
)

Using template literal syntax:

const prose = css`
  ${apply`text-gray-700 dark:text-gray-300`}

  p {
    ${apply`my-5`}
  }

  h1 {
    ${apply`text-black dark:text-white`}
    font-weight: 800;
    font-size: 2.25em;
    margin-top: 0;
    margin-bottom: 0.8888889em;
    line-height: 1.1111111;
  }
`
Using Tailwind directives with animation from twind/css
const motion = animation('.6s ease-in-out infinite', {
  '0%': apply`scale-100`,
  '50%': apply`scale-125 rotate-45`,
  '100%': apply`scale-100 rotate-0`,
})

const bounce = animation(
  '1s ease infinite',
  keyframes`
  from, 20%, 53%, 80%, to {
    ${apply`transform-gpu translate-x-0`}
  }
  40%, 43% {
    ${apply`transform-gpu -translate-x-7`}
  }
  70% {
    ${apply`transform-gpu -translate-x-3.5`}
  },
  90% {
    ${apply`transform-gpu -translate-x-1`}
  }
`,
)
A React button component
import { tw } from 'twind'

const variantMap = {
  success: 'green',
  primary: 'blue',
  warning: 'yellow',
  info: 'gray',
  danger: 'red',
}

const sizeMap = {
  sm: apply`text-xs py(2 md:1) px-2`,
  md: apply`text-sm py(3 md:2) px-2`,
  lg: apply`text-lg py-2 px-4`,
  xl: apply`text-xl py-3 px-6`,
}

const baseStyles = apply`
  w(full md:auto)
  text(sm white uppercase)
  px-4
  border-none
  transition-colors
  duration-300
`

function Button({
  size = 'md',
  variant = 'primary',
  round = false,
  disabled = false,
  className,
  children,
}) {
  // Collect all styles into one class
  const instanceStyles = apply`
    ${baseStyles}
    bg-${variantMap[variant]}(600 700(hover:& focus:&)))
    ${sizeMap[size]}
    rounded-${round ? 'full' : 'lg'}
    ${disabled && 'bg-gray-400 text-gray-100 cursor-not-allowed'}
  `

  // Allow passed classNames to override instance styles
  return <button className={tw(instanceStyles, className)}>{children}</button>
}

render(
  <Button variant="info" className="text-lg rounded-md">
    Click me
  </Button>,
)

Continue to Plugins

Generated using TypeDoc