Twind v1 is still in beta. Expect bugs!
Twind is a small compiler that converts utility-classes into actual CSS rules without any build step right in the browser or any other environment like Node.js, deno, workers, ...
If you have used Tailwind or other CSS-in-JS solutions, then most of the API should feel very familiar.
β‘οΈ No build step
Get all the benefits of Tailwind without the need for Tailwind, PostCSS, configuration, purging, or autoprefixing.
π Framework agnostic
If your app uses HTML and JavaScript, it should work with Twind. This goes for server-rendered apps too.
π One low fixed cost
Twind ships the compiler, not the CSS. This means unlimited styles and variants for one low fixed cost.
Other features include:
- π No bundler required: Usable via CDN
- π¨ Seamless integration with Tailwind
- π€ Feature parity with Tailwind v3
βοΈ Tailwind preflight by default- π― Extended variants, rules, and syntax
- π Escape hatch for arbitrary CSS
- π€ Built in support for conditional rule combining
- π§ Improved readability with multiline styles and comments
- βοΈ Optional hashing of class names ensuring no conflicts
- π© Flexible: configurable theme, rules and variants
- π Language extension via presets
- π© No runtime overhead with static extraction
- π Faster than most CSS-in-JS libraries
- β‘ Fully tree shakeable: Only take what you want
- π¦Ύ Type Strong: Written in Typescript
- and more!
twind does not include any core utilities β use one of the existing presets:
- @twind/preset-autoprefix
- @twind/preset-tailwind to get a full Tailwind v3 experience
- @twind/preset-tailwind-forms to get Tailwind v3 and Tailwind Forms.
- @twind/preset-ext
For the full Tailwind CSS experience try @twind/tailwind or start with Twind CDN a drop-in replacement for Tailwind CSS Play CDN that is almost 6 times smaller (96.4kb vs 16.9kB).
We have created a few examples to get you started:
Example | Try it live at | Description |
---|---|---|
Basic | Stackblitz β’ Codesandbox | with @twind/tailwind |
Playground | Stackblitz β’ Codesandbox | all presets enabled |
SvelteKit | Stackblitz β’ Codesandbox | for SvelteKit with @twind/tailwind |
Tailwind Forms | Stackblitz β’ Codesandbox | with @twind/tailwind and @twind/preset-tailwind-forms |
Twind CDN | Stackblitz β’ Codesandbox | with @twind/cdn |
-
twind β shim-first implementation without any presets to have a clean start
-
@twind/tailwind β shim-first implementation using @twind/preset-tailwind and @twind/preset-autoprefixer
-
API
setup
can be called as many times as you want.- classes are returned in order they are applied by the browser - last one wins
tw
as known from twind v0.16; additional it can be used to access:- the theme:
tw.theme(...)
- the target sheet:
tw.target
- allows to reset twind (start clean):
tw.clear()
- allows to remove twind (remove the associated style element):
tw.destroy()
- the theme:
shortcut
(previously known asapply
) andcss
as known from twind v0.16.- with support for creating named shortcuts:
shortcut.PrimaryButton\
bg-red-500 text-white`->
PrimaryButton#`
- with support for creating named shortcuts:
- new
cx
function to create class names- grouped rules are ungrouped
style
β stitches like component definitions- creates readable class names like
style#1hvn013 style--variant-gray#1hvn013 style--size-sm#1hvn013 style--outlined-@sm-true#1hvn013
- with label:
style({ label: 'button', ... })
button#p8xtwh button--color-orange#p8xtwh button--size-small#p8xtwh button--color-orange_outlined-true$0#p8xtwh
- creates readable class names like
-
grouping syntax:
- allow trailing dash before parentheses for utilities ->
border-(md:{2 black opacity-50 hover:dashed}}
- shortcuts:
~
to apply/merge utilities ->~(text(5xl,red-700),bg-red-100)
- anonymous shortcuts:
~(!text-(3xl center) !underline italic focus:not-italic)
- support comma-separated shortcuts β this would prevent different classNames errors during hydration:
hover:~(!text-(3xl,center),!underline,italic,focus:not-italic)
cx()
converts these to comma-separated group
- support comma-separated shortcuts β this would prevent different classNames errors during hydration:
- named shortcuts:
PrimaryButton~(bg-red-500 text-white)
->PrimaryButton#<hash>
shortcut()
is an helper to simplify creation of shortcuts (works likeapply()
in twind v0.16); it supports creating named shortcuts:shortcut.PrimaryButton\
bg-red-500 text-white`->
PrimaryButton#`
- anonymous shortcuts:
- allow trailing dash before parentheses for utilities ->
-
config
-
presets are executed in order they are defined
-
presets can currently not contain other presets β a work-around may by to use
defineConfig()
within the preset (not tested) -
`defineConfig() helper for typing
-
preset merging:
preflight
β last one winstheme
andtheme.extend
are shallow merged β last one winsrules
,variants
, andignorelist
β first one winshash
andstringify
are overridden if defined by the preset β last one wins
-
user config merging
preflight
β applied lasttheme
andtheme.extend
are shallow merged β applied lastrules
,variants
, andignorelist
β applied firsthash
andstringify
are overridden if defined by the preset β applied first
-
rules and shortcuts based on ideas from UnoCSS
// 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'], // Some shortcuts { // single utility alias red: 'text-red-100', // shortcuts to multiple utilities btn: 'py-2 px-4 font-semibold rounded-lg shadow-md', 'btn-green': 'text-white bg-green-500 hover:bg-green-700', // dynamic shortcut β could be a rule as well 'btn-': ({ $$ }) => `bg-${$$}-400 text-${$$}-100 py-2 px-4 rounded-lg`, }, ], })
There are lots of things possible. See preset-tailwind/rules and preset-ext/rules for more examples.
-
ignorelist: can be used ignore certain rules
This following example matches class names from common libraries:
defineConfig({ // emotion: `css-` // stitches: `c-` // styled-components: `sc-`and `-sc- // svelte: `svelte-` // vanilla-extract: sprinkles_ // goober: `go1234567890` // DO NOT IGNORE rules starting with `^[~#]`, `^css#`, or `^style[~#-]` β these may have been generated by `css()` or `style()`, or are hashed ignorelist: /^((css|s?c|svelte)-|(sprinkles)?_|go\d)|-sc-/, })
-
config theme section function has a changed signature
theme: { - fill: (theme) => ({ + fill: ({ theme }) => ({ gray: theme('colors.gray') }) }
-
no implicit ordering within preflight
-
-
comments (single and multiline)
-
no more important suffix:
rule!
->!rule
-
styles (the generated CSS rules) are sorted predictably and stable β no matter in which order the rules are injected
-
support
label
for a more readable class names (https://emotion.sh/docs/labels) -
support theme(...) in property and arbitrary values
-
no more
@screen sm
-> use the tailwindcss syntax@media screen(sm)
-
@apply finally works as expected
-
full support for color functions:
primary: ({ opacityVariable, opacityValue }) => ...
-
strict tailwindcss v3 compatibility
- no IE 11 fallbacks (color, box-shadow, ...)
- no more
font-*
andtext-*
shortcuts - no
border-tr
butborder-[xytrbl]*
still exists - no
bg-origin-*
-
no more
@global
β you must use&
for nested selectors (this follows the CSS Nesting Module) -
new
@layer
directive following the Cascade Layers (CSS @layer) specThe following layer exist in the given order:
defaults
,base
,components
,shortcuts
,utilities
,overrides
import { css } from 'twind' element.className = css` /* rules with base are not sorted */ @layer base { h1 { @apply text-2xl; } h2 { @apply text-xl; } /* ... */ } @layer components { .select2-dropdown { @apply rounded-b-lg shadow-md; } .select2-search { @apply border border-gray-300 rounded; } .select2-results__group { @apply text-lg font-bold text-gray-900; } /* ... */ } `
-
drop IE 11 support
-
const acx = cx'aaa~(text-blue-500)'
works, butconst bcx = 'bbb~(text-red-500 ${acx})'
not -
support
css()
in preflight and rules -
support
is(:hover,:focus-visible):underline
? -
style: should it pass
class
andclassName
through? alternatives: string concat,cx
-
console.warn(
[twind] unknown rule "${value}"
) -
ci: post on discord after release
-
@twind/tailwind: parse style elements like tailwind?
-
zero runtime
-
cdn.twind.dev -> https://cdn.jsdelivr.net/npm/twind@next/cdn.global.js
-
docs: wmr + codehike + cloudflare pages
-
docs: explain and examples of both modes (observe/shim vs library)
-
docs: common patterns
-
docs: debugging the generated CSS in the browser (cssom sheet)
-
auto support dark mode in theme helpers (
<section>.dark.<key>
ordark.<section>.<key>
) -
@twind/preset-* from tailwind core
-
@twind/react
-
@twind/completions β provide autocompletion for classNames
-
a package to make it easy to create lightweight versions of presets (like https://lodash.com/custom-builds)
-
postcss plugin like tailwindcss for SSR
@twind;
See the Contributing Guide
It would be untrue to suggest that the design here is totally original. Other than the founders' initial attempts at implementing such a module (oceanwind and beamwind) we are truly standing on the shoulders of giants.
- Tailwind: created a wonderfully thought out API on which the compiler's grammar was defined.
- styled-components: implemented and popularized the advantages of doing CSS-in-JS.
- htm: a JSX compiler that proved there is merit in doing runtime compilation of DSLs like JSX.
- goober: an impossibly small yet efficient CSS-in-JS implementation that defines critical module features.
- otion: the first CSS-in-JS solution specifically oriented around handling CSS in an atomic fashion.
- clsx: a tiny utility for constructing class name strings conditionally.
- style-vendorizer: essential CSS prefixing helpers in less than 1KB of JavaScript.
- UnoCSS: for the configuration syntax.
- CSSType: providing autocompletion and type checking for CSS properties and values.
Thank you to all the people who have already contributed to twind!
Thank you to all our sponsors! (please ask your company to also support this open source project by becoming a sponsor)
The MIT license governs your use of Twind.