The instant on-demand Atomic CSS engine

The instant on-demand Atomic CSS engine.

NPM version

💡 I highly recommend reading this blog post –
Reimagine Atomic CSS
for the story behind

🤹‍♂️ Online Playground

Windi CSS, Tailwind CSS, Twind but:

  • Fully customizable – no core utilities, all functionalities are provided via presets.
  • No parsing, no AST, no scanning, it’s INSTANT (200x faster than Windi CSS or Tailwind JIT)
  • ~3.5kb min+gzip – zero deps and browser friendly.
  • Shortcuts – aliasing utilities, dynamically.
  • Attributify Mode – group utilities in attributes
  • Pure CSS Icons – use any icon as a single class.
  • Inspector – inspect and debug interatively.
  • CSS-in-JS Runtime version
  • CSS Scoping
  • VS Code extension
  • Code-splitting for CSS – ships minimal CSS for MPA.
  • Library friendly – ships atomic styles with your component libraries and safely scoped.

all packages.

Refer to the full documentation on Vite:

  • modes: global, dist-chunk, per-module, vue-scoped, and shadow-dom.
  • frameworks: React, Preact, Svelte, SvelteKit, Web Components and Solid.

the default preset. Which provides a common superset of the popular utilities-first framework, including Tailwind CSS, Windi CSS, Bootstrap, Tachyons, etc.

For example, both ml-3 (Tailwind), ms-2 (Bootstrap), ma4 (Tachyons), mt-10px (Windi CSS) are valid.

.ma4 { margin: 1rem; }
.ml-3 { margin-left: 0.75rem; }
.ms-2 { margin-inline-start: 0.5rem; }
.mt-10px { margin-top: 10px; }

Learn more about the default preset.

Dynamic Rules and Variants, you also provide a way to give you full controls of generating the CSS.

By returning a string from the dynamic rule’s body function, it will be directly passed to the generated CSS. That also means you would need to take care of things like CSS escaping, variants applying, CSS constructing, and so on.

import Unocss, { escape as e } from 'unocss'

  rules: [
    [/^custom-(.+)$/, ([, name], { rawSelector, currentSelector, variantHandlers, theme }) => {
      // discard mismatched rules
      if (name.includes('something'))

      // if you want, you can disable the variants for this rule
      if (variantHandlers.length)

      // return a string instead of an object
      return `
.${e(rawSelector)} {
  font-size: ${};
/* you can have multiple rules */
.${e(rawSelector)}::after {
  content: 'after';
.foo > .${e(rawSelector)} {
  color: red;
/* or media queries */
@media (min-width: ${}) {
  .${e(rawSelector)} {
    font-size: ${};

You might need to read some code to take the full power of it.

Windi CSS’s

shortcuts: {
  // 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',
  // single utility alias
  'red': 'text-red-100'

In addition to the plain mapping, UnoCSS also allows you to define dynamic shortcuts.

Similar to Rules, a dynamic shortcut is the combination of a matcher RegExp and a handler function.

shortcuts: [
  // you could still have object style
    'btn': 'py-2 px-4 font-semibold rounded-lg shadow-md',
  // dynamic shortcuts
  [/^btn-(.*)$/, ([, c]) => `bg-${c}-400 text-${c}-100 py-2 px-4 rounded-lg`],

With this, we could use btn-green and btn-red to generate the following CSS:

.btn-green {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  padding-left: 1rem;
  padding-right: 1rem;
  --un-bg-opacity: 1;
  background-color: rgba(74, 222, 128, var(--un-bg-opacity));
  border-radius: 0.5rem;
  --un-text-opacity: 1;
  color: rgba(220, 252, 231, var(--un-text-opacity));
.btn-red {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  padding-left: 1rem;
  padding-right: 1rem;
  --un-bg-opacity: 1;
  background-color: rgba(248, 113, 113, var(--un-bg-opacity));
  border-radius: 0.5rem;
  --un-text-opacity: 1;
  color: rgba(254, 226, 226, var(--un-text-opacity));


We also provide a small collection for you to grab them quickly:

// main.js
// pick one of the following

// normalize.css
import '@unocss/reset/normalize.css'
// reset.css by Eric Meyer
import '@unocss/reset/eric-meyer.css'
// preflights from tailwind
import '@unocss/reset/tailwind.css'

Learn more at @unocss/reset.

Variants allows you to apply some variations to your existing rules. For example, to implement the hover: variant from Tailwind:

variants: [
  // hover:
  (matcher) => {
    if (!matcher.startsWith('hover:'))
      return matcher
    return {
      // slice `hover:` prefix and passed to the next variants and rules
      matcher: matcher.slice(6),
      selector: s => `${s}:hover`,
rules: [
  [/^m-(d)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
  • match controls when the variant is enabled. If the return value is a string, it will be used as the selector for matching the rules.
  • selector provides the availability of customizing the generated CSS selector.

Let’s have a tour of what happened when matching for hover:m-2:

  • hover:m-2 is extracted from users usages
  • hover:m-2 send to all variants for matching
  • hover:m-2 is matched by our variant and returns m-2
  • the result m-2 will be used for the next round of variants matching
  • if no more variant is matched, m-2 will then goes to match the rules
  • our first rule get matched and generates .m-2 { margin: 0.5rem; }
  • finally, we apply our variants transformation to the generated CSS. In this case, we prepended :hover to the selector hook

As a result, the following CSS will be generated:

.hover:m-2:hover { margin: 0.5rem; }

With this, we could have m-2 applied only when users hover over the element.

The variant system is very powerful and can’t be covered fully in this guide, you can check the default preset’s implementation to see more advanced usages.

retain the order of rules, sometimes you may want to group some utilities to have more explicit control of their orders.

Unlike Tailwind, which offers fixed 3 layers (base, components, utilities), UnoCSS allows you to define your own layers as you want. To set the layer, you can pass the metadata as the third item of your rules:

rules: [
  [/^m-(d)$/, ([, d]) => ({ margin: `${d / 4}rem` }), { layer: 'utilities' }],
  // when you omit the layer, it will be `default`
  ['btn', { padding: '4px' }]

This will make it generates:

/* layer: default */
.btn { padding: 4px; }
/* layer: utilities */
.m-2 { margin: 0.5rem; }

You can control the order of layers by:

layers: {
  components: -1,
  default: 1,
  utilities: 2,
  'my-layer': 3,

Layers without specified order will be sorted alphabetically.

When you want to have your custom CSS between layers, you can update your entry module:

// 'uno:[layer-name].css'
import 'uno:components.css'
// layers that are not 'components' and 'utilities' will fallback to here
import 'uno.css'
// your own CSS
import './my-custom.css'
// "utilities" layer will have the highest priority
import 'uno:utilities.css'

@unocss/inspector) for you to view, play and analyse your custom rules and setup. Visit http://localhost:3000/__unocss in your Vite dev server to see it.



MIT License © 2021 Anthony Fu


View Github

Brighton regoûte au succès thumbnail

Brighton regoûte au succès

An easy to use GUI based video to image sequence converter (and vice versa)