1. Core Concepts
  2. Reference

Core Concepts

Reference

Lang

  • comments like in CSS: /* ... */

  • group~{name}-{modifier} and peer~{name}-{modifier}

    Name can contain any characters except whitespace, parenthesis (( and )), colon (:), dash (-), and opening bracket ([).

    html
    <div class="group~project bg-white hover:bg-blue-500 ...">
      <p class="text-gray-900 group~project-hover:text-white ...">New Project</p>
      <div class="group~create bg-gray-100 hover:bg-green-500 ...">
        <p class="text-gray-500 group~create-hover:text-white ...">
          Create a new project from a variety of starting templates.
        </p>
      </div>
    </div>
  • attribute selector as modifier for groups and peers

    html
    <div class="group bg-white hover:bg-blue-500 ...">
      <p class="text-gray-900 group[disabled]:text-gray-200 ...">Project Name</p>
    </div>

    With named groups/peers:

    html
    <div class="group~project bg-white hover:bg-blue-500 ...">
      <p class="text-gray-900 group~project[disabled]:text-gray-200 ...">Project Name</p>
    </div>

CSS (strings and objects)

API

The following functions are all exports from twind.

Tip

If you are using the script tag these methods are available via the twind global object (eg twind.setup).

If you have used Tailwind or other CSS-in-JS solutions, then most of the API should feel very familiar.

Shim Mode

Observe class attributes to inject styles

  • install(config, isProduction): Twind: configures the global tw instance, observes all class attributes and inject the styles into the DOM; returns a twind instance
  • setup(config, sheet?, target?): Twind: configures the global tw instance, observes all class attributes and inject the styles into the DOM; returns a twind instance
  • tw: the global twind instance updated by each setup call

Library Mode

  • twind(config): create a twind instance
  • observe(tw, target?): observes all class attributes and injects the styles into the DOM

Twind instance — tw

  • global twind instance: import { tw } from '@twind/core'

  • tw(className): injects a className string into the sheet and return the resulting class names

  • tw.config: access the current config

  • tw.theme(section?, key?, defaultValue?): access the current theme

    • tw.theme(): returns the whole theme
    • tw.theme(section): returns the whole section
    • tw.theme(dottedKey, defaultValue?): returns the current value
    • tw.theme(section?, key?, defaultValue?): returns the theme value
    js
    tw.theme('colors.blue.500', 'blue')
  • tw.target: the sheet target of this instance (string[], HTMLStyleSheet, CSSStyleSheet)

  • tw.clear(): clears all CSS rules from the sheet

  • tw.destroy(): remove the sheet from the document

Utilities

  • defineConfig(config): define a configuration object for setup or twind

  • tx(...args): creates a class name from the given arguments and injects the styles (like tw(cx(...args)))

    • tx.bind(tw): binds the tx function to a custom twind instance; returns a new tx function
    js
    import { tx } from '@twind/core'
    
    const className = tx`underline bg-red-200 text-red-900`
  • injectGlobal(...args): injects the given styles into the base layer

    • injectGlobal.bind(tw): binds the injectGlobal function to a custom twind instance; returns a new injectGlobal function
    js
    import { injectGlobal } from '@twind/core'
    
    injectGlobal`
      @font-face {
        font-family: "Operator Mono";
        src: url("../fonts/Operator-Mono.ttf");
      }
    
      body {
        margin: 0;
      }
    `
  • keyframes(...args): lazily injects the keyframes into the sheet and return a unique name

    • keyframes.Name(...args): lazily injects the named keyframes into the sheet and return a unique name
    • keyframes.bind(tw): binds the keyframes function to a custom twind instance; returns a new keyframes function
    js
    import { keyframes, css, tx } from '@twind/core'
    
    const fadeIn = keyframes`
      0% {
        opacity: 0;
      }
      100% {
        opacity: 1;
      }
    `
    // using CSS object notation
    const fadeIn = keyframes({
      '0%': { opacity: 0 },
      '100%': { opacity: 1 },
    })
    
    // within a arbitrary value
    el.className = `animate-[1s_${fadeIn}_ease-out]`
    
    // within CSS
    const fadeInClass = css`
      animation: 1s ${fadeIn} ease-out;
    `
  • animation(animation: string | CSSProperties, waypoints: StringLike): lazily injects the animation into the sheet and return a unique name

    • animation.Name(animation: string | CSSProperties, waypoints: StringLike): lazily injects the named animation into the sheet and return a unique name
    js
    import { animation, keyframes } from '@twind/core'
    
    const fadeIn = animation(
      '1s ease-out',
      keyframes`
        0% {
          opacity: 0;
        }
        100% {
          opacity: 1;
        }
      `,
    )

Helper functions

These generate class names but do not inject styles.

These can be used to generate class names that are then

a) set the class attribute on an element (Shim Mode)
b) used with tw to inject styles and return a class name (Library Mode)

  • cx(...args): creates a class name from the given arguments; no styles injected

    js
    import { cx } from '@twind/core'
    
    // Set a className
    element.className = cx`
      underline
      /* multi
        line
        comment
      */
      hover:focus:!{
        sm:{italic why}
        lg:-{px}
        -mx-1
      }
      // Position
      !top-1 !-bottom-2
      text-{xl black}
    `
  • css(...args): creates a class name from the given arguments; no styles injected

  • style(options): creates a stitches like helper; returns a style function

    • style(props): creates a class name from the given props; no styles injected

Mostly server side

Used to update an html string with styles.

  • inline(html, tw? | {tw, minfiy}?): updates all class attributes in html string and inject there styles into the head as style element; returns the updated html string
  • extract(html, tw?): updates all class attributes from html string; returns the updated html string and the CSS
  • consume(html, tw?): updates all class attributes from html string; returns the updated html string and injects all styles into tw

Sheets

  • getSheet(useDOMSheet?: boolean, disableResume?: boolean): returns a Sheet for the current environment — virtual on server, either dom or cssom in browsers
  • virtual(includeResumeData?: boolean): collect styles into an array
  • cssom(element?: CSSStyleSheet | Element | null | false): uses a fast DOM sheet — bad for debugging
  • dom(element?: Element | null | false): uses a slow DOM sheet — great for debugging
  • stringify(target): returns the CSS string of a sheet target

Config

Dark Mode

js
defineConfig({
  // using media strategy with `@media (prefers-color-scheme:dark)`
  darkMode: 'media',
  // using class strategy with `.dark`
  darkMode: 'class',
  // custom selectors
  darkMode: '.dark-mode',
  darkMode: '[theme=dark]',
})

Auto Dark Colors

If enabled, automatic dark colors are generated for each light color (eg no dark: variant is present). This feature is opt-in and twind provides a builtin function that works with tailwind color palettes (50, 100, 200, ..., 800, 900).

ts
import { autoDarkColor } from '@twind/core'

defineConfig({
  // for tailwind color palettes: 50 -> 900, 100 -> 800, ..., 800 -> 100, 900 -> 50
  darkColor: autoDarkColor,
  // other possible implementations
  darkColor: (section, key, { theme }) => theme(`${section}.${key}-dark`) as ColorValue,
  darkColor: (section, key, { theme }) => theme(`dark.${section}.${key}`) as ColorValue,
  darkColor: (section, key, { theme }) => theme(`${section}.dark.${key}`) as ColorValue,
  darkColor: (section, key, context, lightColor) => generateDarkColor(lightColor),
})

Example css for text-gray-900:

css
.text-gray-900 {
  --tw-text-opacity: 1;
  color: rgba(15, 23, 42, var(--tw-text-opacity));
}
@media (prefers-color-scheme: dark) {
  .text-gray-900 {
    --tw-text-opacity: 1;
    color: rgba(248, 250, 252, var(--tw-text-opacity));
  }
}

The auto-generated dark color can be overridden by the usual dark:... variant: text-gray-900 dark:text-gray-100.

css
.text-gray-900 {
  --tw-text-opacity: 1;
  color: rgba(15, 23, 42, var(--tw-text-opacity));
}
@media (prefers-color-scheme: dark) {
  .text-gray-900 {
    --tw-text-opacity: 1;
    color: rgba(248, 250, 252, var(--tw-text-opacity));
  }
}
@media (prefers-color-scheme: dark) {
  .dark\\:text-gray-100 {
    --tw-text-opacity: 1;
    color: rgba(241, 245, 249, var(--tw-text-opacity));
  }
}

Rules

based on ideas from UnoCSS

js
// defineConfig is optional but helps with type inference
defineConfig({
  rules: [
    // Some rules
    ['hidden', { display: 'none' }],

    // Table Layout
    // .table-auto { table-layout: auto }
    // .table-fixed { table-layout: fixed }
    ['table-(auto|fixed)', 'tableLayout'],

    // dynamic
    ['table-', (match, context) => /* ... */],

    // Some aliases
    // shortcut: styles are generated as defined by twind — same as if they where used alone
    // shortcut to multiple utilities
    ['card', 'py-2 px-4 font-semibold rounded-lg shadow-md'],

    // dynamic shortcut — `$$` is everything after the match eg `btn-red` -> `red`
    ['card-', ({ $$ }) => `bg-${$$}-400 text-${$$}-100 py-2 px-4 rounded-lg`],

    // single utility alias — need to use `~(...)` as it would be otherwise recognized as a CSS property
    ['red', '~(text-red-100)'],

    // apply: styles are generated in order they are declared
    // apply to multiple utilities
    ['btn-green', '@(bg-green-500 hover:bg-green-700 text-white)'],

    // dynamic apply
    ['btn-', ({ $$ }) => `@(bg-${$$}-400 text-${$$}-100 py-2 px-4 rounded-lg)`],

    // Using cx
    ['highlight(-rounded)?', ({ 1: rounded }) => cx({ 'bg-yellow-200': true, rounded })],

    // Using css
    [
      'target-new-tab',
      css`
        target-name: new;
        target-new: tab;
      `,
    ],
    // dynamic
    [
      'target-new-(tab|window)',
      ({ 1: $1 }) => css`
        target-name: new;
        target-new: ${$1};
      `,
    ],

    // Using style
    // `box?color=coral` -> `.box\\?color\\=coral{background-color:coral}`
    // `box?rounded` -> `.box\\?rounded{border-radius:0.25rem}`
    // `box?color=coral&rounded` -> `.box\\?color\\=coral\\&rounded{background-color:coral;border-radius:0.25rem}`
    // `box?color=purple&rounded=md` -> `.box\\?color\\=purple\\&rounded\\=md{background-color:purple;border-radius:0.375rem}`
    [
      'box\\?(.+)',
      style({
        props: {
          color: {
            coral: css({
              backgroundColor: 'coral',
            }),
            purple: css`
              background-color: purple;
            `,
          },
          rounded: {
            '': 'rounded',
            md: 'rounded-md',
          },
        },
      }),
    ],
  ],
})

Hash

hash all shortcuts and apply

js
defineConfig({
  hash(className, defaultHash) {
    if (/^[~@]\(/.test(className)) {
      // a shortcut like `~(...)`
      // an apply like `@(...)`
      return defaultHash(className)
    }

    return className
  },
})

add namespace/scope to all classes

js
defineConfig({
  hash(className) {
    return `.scoped ${className}`
  },
})

Browser Support

In general, Twind is designed for and tested on the latest stable versions of Chrome, Firefox, Edge, and Safari. It does not support any version of IE, including IE 11.

For automatic vendor prefixing include the @twind/preset-autoprefix preset.

For more details see Tailwind CSS › Browser Support.

The following JS APIs may need polyfills:

When using style() within config.rules: