WindiCSS v3.0 now in Beta

Developers, it's been a long time, I'm voorjaar. And here is windicss v3.0.


Attributify Mode

First, special thanks to @Tahul and @antfu for sharing this idea. When I first saw this, I was shocked by the beauty of it. Let me give you an example, and I believe you will feel the same as me.

First of all, this is the utility syntax used by the traditional tailwind, that is, all utilities and variants are written in the class.

<button class="bg-blue-400 hover:bg-blue-500 text-sm text-white font-mono font-light py-2 px-4 rounded border-2 border-blue-200 dark:bg-blue-500 dark:hover:bg-blue-600">

Then, the following is the Attributify mode by windi:

	bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  text="sm white"
  font="mono light"
  p="y-2 x-4"
  border="2 rounded blue-200"

Try it now!

The beta version of vite-plugin already has the support for Attributify. You can follow the instructions to install and try it out today! Integrations for other frameworks/tools will follow up soon.

Why use it?


When you are writing using traditional class syntax, there is usually no clear order and classification requirements, which allows you to write as you like, but when you come back a few months to look at the components you wrote, you don't even know what you wrote.

Then the code written through the attributify mode is very neat, even after a long time, you only need to look at the key at the beginning to understand which parts of the component are used.


All utilities are perfectly divided into different parts, each completes a different division of labor, so that you can easily know what kind of utility each element uses. Different types of utilities will never be placed in the same group. This is actually a bit similar to typescript, forcing you to add types. Of course, we all know the benefits of doing so.


You can not only use utilities, but also combine with variants to achieve very complex operations. such as bg = "blue-500 dark:blue-600". And you can still use the class attribute, you can add your own shortcuts or classes from other frameworks to it.


We compile utilities into css based on css attribute selector to make html native support.

[bg~="blue-400"] {
  --tw-bg-opacity: 1;
  background-color: rgba(96, 165, 250, var(--tw-bg-opacity));


You actually have two programming paradigms to choose from:

  1. Grouping based on utilities

  2. Grouping based on variants


And of course, you can also mix them, but I personally don't recommend. Just choosing one and stick to it. You will find your code becomes clearer.


  1. If you think the attribute name may conflict with the html attribute, you can set a prefix through attributify.prefix config in windi.config.ts.
  2. You can setup attributify config to set whether to enable the attributify mode.
  3. Regarding the issue of css size, the css size may be slightly increased, but under gzip compression, the impact is minimal.
  4. We are developing a project that allows you to freely switch between classic and attributify modes, and even allows you to use attributify mode during development and classic mode during production.
  5. You can check another article for whole attributify mode details.

New Colors

We brought two new colors, dark and light. Dark colors are particularly useful when making dark mode. Light colors can be used in the background, for example, to create a little contrast between nav and container.

3.0 is still in the beta stage, so if you are a professional designer, you are welcome to make some suggestions for these two sets of colors. Thank you very much.





Alias Config

We introduced shortcuts in the v2.2 version, allowing you to quickly reuse some commonly used combinations. For me, I like to use these shortcuts in the project, it will make my project look cleaner and tidy. I guess you all have your way of using it.

export default {
  shortcuts: {
    'hstack': 'flex items-center',
    'vstack': 'flex flex-col',
    'icon': 'w-6 h-6 fill-current',
    'app-border': 'border-gray-200 dark:border-dark-300',
    'app-modal': 'fixed top-0 w-full h-full z-50 bg-white bg-opacity-70 blur-5 shadow-lg',
    // ...

Back to the topic, the way shortcuts works is to first build a separate class, such as .hstack. Then we thought about whether we can achieve the same thing by using simple text replacement without compiling. I heard that some people are very obsessed with using a single utility. So you just got it. With alias config, actually the syntax is similar to shortcuts, except that complex nested css is not supported.

export default {
  alias: {
    hstack: 'flex items-center',
    // ...

When using shortcuts, you can call it directly through the class, because it is the class, so you can do this.

<div class="hstack">

But with alias. You must give it a tag to identify it is an alias, we choosed the char * to do this. It's actually inspired by pointers in C language.

<div class="*hstack">

This syntax is exactly equivalent to

<div class="flex items-center">

tips: alias in group <div class="sm:(bg-blue-400 *hstack)"></div> is supported, cause it's just text replacement.

New Utilities

Changes & Bug Fixes

  • change variant all-child to children
  • change variant sibling to sibilings: & ~ *
  • add variant sibling: & + *
  • fixes: #206 #262 #246 #245 #243 #269

One More Thing

One decision I regretted the most was to move the feature should be implement in 4.0 to 3.0, and then I did it for about two weeks without finishing it. That is windi lang. Then you may ask why?

Imagine you can create a css file with a simple windi syntax.

@apply font-bold text-lg; /* generate utilities .font-bold .text-lg */

.button {
  @apply px-4 py-2 bg-blue-400 text-white hover:bg-blue-500;

.button-red: px-4 py-2 bg-red-500 text-gray-100; /* more simple one line version */

/* generate all different colors */
@for color in colors {
  @apply bg-${color};

/* support nested css and variables */
@var borderWidth = 1rem;
.button {
  &:hover {
    border: ${borderWidth + 4px} outset blue;
    background: rbga(${theme('blue.400').toRGB().join(', ')}, var(--button-opacity));

/* what about setup config only works in this scope */
@config local {
  colors: {

/* directly interact with JavaScript */
@js {
  import { eval, rgba, get, set } from 'windi/lang';

  const a = get('width');
  set('width', eval('4px'));
  const width = eval('3px');
  export function add(a, b) {
    return a + b;

.hello {
  width: ${add(1,2)}rem;

We are not far from completion. Check out another article on windi lang to learn about the syntax. And your suggestions will be very valuable.

Finally, sincerely thank our sponsors, because of you we have come here.

Special Sponsor


Become a sponsor on Open Collective