{"id":17912054,"url":"https://github.com/arnaudbarre/downwind","last_synced_at":"2026-04-11T10:14:49.156Z","repository":{"id":58033984,"uuid":"512332779","full_name":"ArnaudBarre/downwind","owner":"ArnaudBarre","description":"A bundler-first \u0026 PostCSS-independent implementation of Tailwind","archived":false,"fork":false,"pushed_at":"2024-11-27T16:36:50.000Z","size":1432,"stargazers_count":64,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-31T04:08:11.316Z","etag":null,"topics":["lightningcss","tailwind"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ArnaudBarre.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-10T03:08:43.000Z","updated_at":"2024-12-09T08:37:33.000Z","dependencies_parsed_at":"2023-01-30T18:45:26.940Z","dependency_job_id":"4b0a38e6-3102-4372-82af-8a8c77794c72","html_url":"https://github.com/ArnaudBarre/downwind","commit_stats":{"total_commits":69,"total_committers":1,"mean_commits":69.0,"dds":0.0,"last_synced_commit":"e070ea0836ea85c2245d704f1f5d31849a2c0c87"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArnaudBarre%2Fdownwind","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArnaudBarre%2Fdownwind/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArnaudBarre%2Fdownwind/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArnaudBarre%2Fdownwind/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ArnaudBarre","download_url":"https://codeload.github.com/ArnaudBarre/downwind/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247595334,"owners_count":20963943,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lightningcss","tailwind"],"created_at":"2024-10-28T19:42:53.975Z","updated_at":"2026-04-11T10:14:49.150Z","avatar_url":"https://github.com/ArnaudBarre.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# downwind [![npm](https://img.shields.io/npm/v/@arnaud-barre/downwind)](https://www.npmjs.com/package/@arnaud-barre/downwind)\n\nA bundler-first \u0026 PostCSS-independent implementation of Tailwind.\n\nInspired by [UnoCSS](https://github.com/unocss/unocss).\n\n## Usage with [Vite](https://vitejs.dev/)\n\n```ts\n// vite.config.ts\nimport { downwind } from \"@arnaud-barre/downwind/vite\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n  // keep the plugin before any other plugins that do code transformations\n  plugins: [downwind()],\n  css: {\n    transformer: \"lightningcss\",\n  },\n  build: {\n    cssMinify: \"lightningcss\",\n  },\n});\n```\n\nAdd `import \"virtual:@downwind/base.css\";` and `import \"virtual:@downwind/utils.css\";` to your code.\n\n[Like unocss](https://unocss.dev/integrations/vite#edit-classes-in-devtools), you can also add `import \"virtual:@downwind/devtools\";` to get autocomplete and on-demand CSS in the browser. The same warning apply:\n\n\u003e ⚠️ Please use it with caution, under the hood we use [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to detect the class changes. Which means not only your manual changes but also the changes made by your scripts will be detected and included in the stylesheet. This could cause some misalignment between dev and the production build when you add dynamic classes based on some logic in script tags. We recommended adding your dynamic parts to the safelist or setup UI regression tests for your production build if possible.\n\n## Usage with [esbuild](https://github.com/evanw/esbuild)\n\n```ts\nimport { downwind } from \"@arnaud-barre/downwind/esbuild\";\nimport { build } from \"esbuild\";\n\nawait build({\n  bundle: true,\n  // entryPoints, sourcemap, minify, outdir, target, ...\n  plugins: [downwind()],\n});\n```\n\nAdd `import \"virtual:@downwind/base.css\";` and `import \"virtual:@downwind/utils.css\";` to your code.\n\n## Scanned extensions\n\nBy default, only `.tsx` files and `.ts` files containing `@downwind-scan` are scanned. This can be changed in both plugins:\n\n```ts\n// vite\nplugins: [\n  downwind({\n    shouldScan: (id, code) =\u003e\n      id.endsWith(\".vue\")\n      || (id.endsWith(\".ts\") \u0026\u0026 code.includes(\"@downwind-scan\")),\n  }),\n];\n// esbuild\nplugins: [\n  downwind({\n    filter: /\\.(vue|ts)$/,\n    shouldScan: (path, code) =\u003e\n      path.endsWith(\".vue\") || code.includes(\"@downwind-scan\"),\n  }),\n];\n```\n\n## Configuration\n\nThis is optional and can be used to customize the default theme, disable core rules, add new rules, shortcuts or a safelist.\n\nThe file should be named `downwind.config.ts`.\n\n```ts\nimport { DownwindConfig } from \"@arnaud-barre/downwind\";\n\nexport const config: DownwindConfig = {\n  // ...\n};\n```\n\nThis can also be computed asynchronously:\n\n```ts\nexport const config: DownwindConfig = async () =\u003e {\n  return {\n    // ...\n  };\n};\n```\n\n## Differences with Tailwind\n\nThe current implementation aligns with Tailwind 3.4.2.\n\n### Components\n\nDownwind doesn't have the notion of components, but custom rules can be injected before core rules by using `injectFirst: true`.\n\nShortcuts from [Windi CSS](https://windicss.org/features/shortcuts.html#shortcuts) solves most the needs and can be added to the configuration:\n\n```ts\n// downwind.config.ts\nimport { DownwindConfig } from \"@arnaud-barre/downwind\";\n\nexport const config: DownwindConfig = {\n  shortcuts: {\n    \"btn\": \"py-2 px-4 font-semibold rounded-lg shadow-md\",\n    \"btn-green\": \"text-white bg-green-500 hover:bg-green-700\",\n  },\n};\n```\n\n### Arbitrary values\n\nFew complex cases are not implemented to keep the implementation lean and fast:\n\n- `backgroundImage`, `backgroundPosition` and `fontFamily` are not supported\n- For prefix with collision (divide, border, bg, gradient steps, stroke, text, decoration, outline, ring, ring-offset), if the value doesn't match a CSS color (hex, rgb\\[a], hsl\\[a]) or a CSS variable it's interpreted as the \"size\" version. Using data types is not supported\n- Underscore are always mapped to space\n- The theme function is not supported\n\n[Arbitrary properties](https://tailwindcss.com/docs/adding-custom-styles#arbitrary-properties) can be used to bypass some edge cases.\n\n### Theme colors\n\nThe color palette is flat, so colors should be defined like: `\"blue-300\": \"#93c5fd\", \"blue-400\": \"#60a5fa\"` instead of `blue: { 300: \"#93c5fd\", 400: \"#60a5fa\" }`\n\n### Dark mode\n\nOnly the `class` strategy is supported with a simple `.dark \u0026` selector rewrite\n\n### Plugins\n\nTailwind plugins are incompatible, but can probably be re-written using Downwind rules. Go to the types definition to get more information.\n\nFor simple utilities, you can use `staticRules`:\n\n```ts\n// downwind.config.ts\nimport { DownwindConfig, staticRules } from \"@arnaud-barre/downwind\";\n\nexport const config: DownwindConfig = {\n  rules: [\n    ...staticRules({\n      \"overflow-x-auto\": { \"overflow-x\": \"auto\" },\n      \"overflow-y-auto\": { \"overflow-y\": \"auto\" },\n    }),\n  ],\n};\n```\n\n### Dynamic variants\n\n`supports-*`, `min-*`, `max-*`, `has-*`, `(group/peer-)data-*`, `(group/peer-)aria-*` are supported.\n\n`max-\u003cscreen\u003e` is supported when the screens config is a basic `min-width` only. No sorting is done.\n\n`group-*`, `peer-*` and variants modifier (ex. `group/sidebar`) are not supported. The few cases were there are needed can be covered with arbitrary variants:\n`group-hover/sidebar:opacity-75 group-hover/navitem:bg-black/75` -\u003e `[.sidebar:hover_\u0026]:opacity-75 group-hover:bg-black/75`\n\n### Variants\n\n- `marker` and `selection` variants don't apply on children\n- `visited` variant doesn't remove opacity modifiers\n\n### BoxShadow and ring utilities\n\nBoth rely on box-shadow to work. The current implementation is way simpler than the Tailwind one, so both utilities can't be used at the same time.\n\nFor colored box shadows, you need to use this config format:\n\n```ts\n\"md\": {\n  value: \"0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color)\",\n  defaultColor: \"rgb(0 0 0 / 0.1)\",\n}\n```\n\n### Flex utility\n\n`display: flex` is automatically included in some cases:\n\n- `flex flex-col` -\u003e `flex-col`\n- `flex flex-row-reverse` -\u003e `flex-row-reverse`\n- `flex flex-col-reverse` -\u003e `flex-col-reverse`\n- `flex flex-wrap` -\u003e `flex-wrap`\n- `flex flex-wrap-reverse` -\u003e `flex-wrap-reverse`\n\n### Space utility\n\nCan be overridden by margin utility and doesn't work for flex-reverse. I highly recommend migrating to [flex gap](https://caniuse.com/flexbox-gap).\n\n### Inset utility\n\nAngle utility are available ex: `insert-tr-2`\n\n### Divide utility\n\nSimpler implementation that makes divide and divide-reverse independent. Naming is updated to avoid an implementation edge case.\n`divide-y divide-y-reverse` -\u003e `divide-reverse-y`\n\n### VerticalAlign utility\n\nCan be customized via theme. Mostly useful to allow arbitrary values without a specific edge case.\n\n### Cursor utility\n\nThe possible values are not configurable via theme.\n\n### Theme function\n\nTo avoid parsing errors in WebStorm, double quotes are required. And because [the palette color is flat](#theme-colors), any configuration value is accessed via `theme(\"key.value\")`:\n\n- `theme(borderRadius.lg)` -\u003e `theme(\"borderRadius.lg\")`\n- `theme(colors.blue.500 / 75%)` -\u003e `theme(\"colors.blue-500 / 75%\")`\n- `theme(spacing[2.5])` -\u003e `theme(\"spacing.2.5\")`\n\n### Almost exhaustive list of non-supported features\n\n- Container queries, but this will probably be added later\n- Adding variants via plugins. Also something useful to support in the future\n- [prefix](https://tailwindcss.com/docs/configuration#prefix), [separator](https://tailwindcss.com/docs/configuration#separator) and [important](https://tailwindcss.com/docs/configuration#important) configuration options\n- These deprecated utils: `transform`, `transform-cpu`, `decoration-slice` `decoration-clone`, `filter`, `backdrop-filter`, `blur-0`\n- These deprecated colors: `lightBlue`, `warmGray`, `trueGray`, `coolGray`, `blueGray`\n- Using multiple group and peer variants (i.e. `group-active:group-hover:bg-blue-200` doesn't work)\n- `@tailwind`, `@config` and `@layer` directives\n- `@apply` for anything else than utils\n- `!important` at the end of `@apply` statement\n- Using pre-processor like `Sass` or `less`\n- `border-spacing` utility\n- Negative utilities when using min/max/clamp\n- `rtl` variant \u0026 logical properties for inline direction\n- Multi-range breakpoints \u0026 custom media queries in screens\n- Sorting of extended screens with default ones\n- Object for keyframes definition\n- Multiple keyframes in animation\n- Wrap attribute with quotes when using `data-` and `aria-`\n- Automatic var injection in arbitrary properties\n- Letter spacing \u0026 font weight in fontSize theme\n- Font feature \u0026 variation settings in fontFamily theme\n- Regular expressions in safelist\n\n## How it works\n\nWhen loading the configuration, four maps are generated: one for static variants, one for prefixes of dynamic variants, one for static rules and one for prefixes of arbitrary values.\n\nThen an object with few methods is returned:\n\n```ts\n{\n  getBase: () =\u003e string;\n  preTransformCSS: (content: string) =\u003e {\n    invalidateUtils: boolean;\n    code: string;\n  };\n  scan: (code: string) =\u003e boolean /* hasNewUtils */;\n  generate: () =\u003e string;\n}\n```\n\n- `getBase` returns the static preflight, identical to Tailwind. Init of CSS variables like `--tw-ring-inset` are included in the \"utils\", which remove the need for base to be processed with utils.\n- `preTransformCSS` is used to replace `@apply`, `@screen` \u0026 `theme()` in CSS files. Some utils may depend on CSS variable injected in the header of utils, so `invalidateUtils` can be used during development to send an HMR update or refresh the page.\n- `scan` is used to scan some source code. A regex is first use to match candidates and then these candidates are parsed roughly like this:\n  - Search for variants (repeat until not match):\n    - If the token start `[`, looks for next `]:` and add the content as arbitrary variant. If no `]:`, test if it's an arbitrary value (`[color:red]`).\n    - else search `:`\n      - if the left part contains `-[`, search for the prefix in the dynamic variant map\n      - otherwise lookup the value in the static variant map\n  - Test if the remaining token is part of the static rules\n  - Search for `-[`\n    - if matchs:\n      - search for the prefix in the arbitrary values maps, if not bail out\n      - search for `]/`\n        - if matchs, parse the left as arbitrary value and thr right as modifier\n        - else if ends with `]`, parse the left as arbitrary value\n    - else search for `/`, parse search for the left in the static rules map and parse the end as a modifier\n\nIf the token matches a rule and is new it's added to an internal map structured by media queries. `true` is returned and this can be used to invalidate utils in developments.\n\n- `generate` is used to transform the recursive map into a CSS output. This is returned as the content of `virtual:@downwind/utils.css` in plugins.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnaudbarre%2Fdownwind","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farnaudbarre%2Fdownwind","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnaudbarre%2Fdownwind/lists"}