JavaScript based linters and formatters can access Tailwind CSS data directly, since Tailwind CSS itself is written is exposed as JavaScript modules.
However, non-JS tools like Biome and Oxc don't have this advantage. To pull off CSS class sorting, some information is necessary (available utilities and variants, ordering-related information about them, possible values, etc).
My aim with this "spec" is to iterate on a possible format for this data, since the Tailwind CSS team is agreeable to the idea of providing this information through a stable API directly from Tailwind CSS. This way, the extracted information can be incorporated in these tools either by being built into them as a preset, or through config.
- Enable the creation of a preset.
"Preset" meaning this sort-related information on a clean, default Tailwind CSS environment (no added plugins, utilities, variants, etc).
- Enable updates to the preset.
When new Tailwind CSS versions are released, they might contain new utilties or variants, so we need to make sure we have an easy and correct way of keeping things in sync. It could also enable automating the process through a CI action.
- Enable support for (Tailwind CSS) plugins and user-provided utilities and variants.
Users can augment and customize Tailwind CSS through plugins, or add their own utilities and variants. We need that information so that users can provide it to the formatter tool.
- Create a standard for other utility class tools.
Tailwind CSS is the clear leader between all tools that work with the concept of utility classes, but it's not the only one (e.g. UnoCSS). While it is the priority, ideally this format can be useful to other tools besides Tailwind CSS.
Here's the data that is necessary to properly sort CSS classes. Note that this spec doesn't go into the specifics of the sorting algorithm unless important for context.
Utilities are grouped in layers. Specifically, "base", "utilities" and "components" in Tailwind CSS.
Layers are ordered.
E.g. m-2 (margin) or font-bold.
In order to sort utilities, the following information about them is needed:
- The layer they belong to. Utilities are ordered within a layer.
- Whether they are "absolute" or a "prefix". For example,
"block"(display: block) is "absolute", complete by itself."m-"is a prefix because it needs to be completed, e.g.m-2.
❓ May or may not need:
-
Whether they support arbitrary values.
The reason why this might not be needed is because the current sorting implementation in Biome is "prefix-based" (more about this below).
A reason why it MIGHT be needed is ambiguity between similar utilities. See the section below about values for more context.
Note: "parasitic" utilities (e.g.
"group") are also necessary, so they can be declared in the first layer to imitate the sorting behavior of Tailwind CSS.
E.g. hover: or focus.
Variants are ordered.
❓ May or may not need:
-
Whether the variant supports arbitrary values (e.g.
data-[size=large]:).This hasn't been implemented in Biome yet, so I'm not sure if it will be necessary.
❓ TODO:
- Consider compound variants (upcoming in v4)
The Biome implementation of sorting is "prefix-based", i.e. when matching the margin utility (e.g. m-2), the content after m- can be anything as we're only looking for the prefix. m-[2px], m-2, m-asdf and m-%28jhu213g are all the same to Biome.
This makes things simpler by not requiring values to identify utilities and sort them. However, there is a problem: some utilities can share a prefix.
For example, there's a utility that sets the text color (color CSS property), and a different utility that sets the wrapping behavior (text-wrap CSS property). However, both use the text- prefix (e.g. text-red-500 and text-nowrap). To tell them apart, we need to look at the value, therefore values are necessary information.
This is further complicated by the following factors:
- Some utilities support arbitrary variants which means we do not only need specific values (
red-500) but also the supported data types per-utility (e.g. "color" so thattext-[#abc123]is supported. Related: https://tailwindcss.com/docs/adding-custom-styles#resolving-ambiguities - note for myself: look atinferDataTypeimplementation. - Tailwind CSS v4 introduces the possibility of writing arbitrary values in non-arbitrary syntax, e.g.
w-21361without those values existing prior to usage.
Here's how I envision this data being structured:
{ "layers": [{ "name": "utilities", "utilities": [{ "name": "block", "absolute": true }, { "name": "text", // used for matching (`text-<whatever>`) // note: if there's a more descriptive name that can be pulled (e.g. text color), // it could be included in a "description" field for better debuggeability and DX, // but it's a nice to have and completely optional "supports-arbitrary": true, // might not be necessary "data-types": ["color"], // for arbitrary value disambiguation "values": ["$color"] // to keep the size small, whenever possible, reference values // by categories rather than repeating every time }, { "name": "text", "supports-arbitrary": true, // might not be necessary "data-types": ["length"], // for arbitrary value disambiguation "values": ["$scale"] }, { "name": "text", "supports-arbitrary": false, // might not be necessary "data-types": ["length"], // for arbitrary value disambiguation "values": ["wrap", "nowrap", "balance", "pretty"] // reference could work too, // but this is okay for // one-off lists of values }] }, // ... ], "variants": [{ "name": "hover", // used for matching (`hover:`) "supports-arbitrary": false, // might not be necessary }], "values": { "color": [ "red-100", "red-200", // ... // potentially also support this for brevity { "red": ["100", "200", /* ... */] } ], "length": [ // ... ] } }