A cli tool for TailwindCSS that allows you to apply one stack of modifiers to several utility classes. It's a postprocessor, so you write normal and valid tailwind classes, intellisense works as intended, warnings are helpful and the type of the frontend framework is irrelevant.
npm install tailwind-modifier-aliasing
tailwind-ma input.css output.css [--watch]
To run in parallel with tailwind in watch mode in bash:
tailwind -i input.css -o output.css -w & tailwind-ma output.css final.css -w
First, some of the tailwind classes have to be written in a particular way. Then, this cli tool takes the tailwind generated and unminified output file and rewrites some of the rules to take advantage of native css nesting.
A utility class that defines a property named alias
can have its modifiers applied to utility classes that prepend their selectors with the the value of this property. Such classes can be said to depend on the presence of the aliased modifier class that they reference. Take this markup:
<div class="hover:md:[alias:hm] [hm&]:bg-red-500">
Red on hover when wider than 768px
</div>
Here hm
is an alias for hover:md
, and [hm&]
is a reference that can be used to apply the modifiers to bg-red-500
. Given this markup tailwind generates these two rules:
@media (min-width: 768px) {
.hover\:md\:\[alias\:hm\]:hover {
alias: hm;
}
}
hm.\[hm\&\]\:bg-red-500 {
//...
}
which are then rewritten by this tool into this:
@media (min-width: 768px) {
:is(.hover\:md\:\[alias\:hm\]:hover) {
&.\[hm\&\]\:bg-red-500 {
//...
}
}
}
which has the same effect as hover:md:bg-red-500
.
An element can have several aliased classes, each must have a different alias to avoid conflicts. Also, dependent classes can have additional modifiers of their own.
<div class="hover:peer-[:not(:placeholder-shown)]:[alias:a] [a&]:bg-blue-300 [a&]:text-sm [a&]:font-bold group-focus/item:last-of-type:[alias:b] [b&]:bg-red-500 [b&]:hover:text-lg [b&]:hover:italic">
The syntax is quite verbose and only suitable for taming some of the more egregious modifiers, but with native support it could look like this hover:md::h h:bg-red-500 h:text-white h:font-bold
which is almost as concise as other grouping proposals.
Aliases are global. This tool doesn't examine source files and has no idea which classes actually depend on which. So if the same alias is used by different modifier classes that are dependent on by many other utilities then all their permutations must be supported. Given this markup:
<div class="hover:[alias:a] [a&]:bg-red-500">
Red
</div>
<div class="focus:[alias:a] [a&]:bg-blue-500" tabindex="0">
Blue
</div>
tailwind generates this css:
.hover\:\[alias\:a\]:hover {
alias: a;
}
.focus\:\[alias\:a\]:focus {
alias: a;
}
// ...two more rules for red and blue
and now both bg-red-500
and bg-blue-500
rules have to be copied into each of those aliased rules. This tool tries to tackle the problem by deduplicating aliased rules, so the final css is actually this:
:is(.focus\:\[alias\:a\]:focus,
.hover\:\[alias\:a\]:hover) {
&.\[a\&\]\:bg-blue-500 {
//...
}
&.\[a\&\]\:bg-red-500 {
//...
}
}
Wrapping the selector list in the :is
pseudo-class function ensures that one unsupported selector doesn't invalidate the entire list.
Deduping is only possible for aliased rules that belong to the same at-rule. For example, given these classes: hover:md:[alias:a]
, hover:sm:[alias:a]
, hover:md:dark:[alias:a]
, hover:[alias:a]
tailwind generates this css:
.hover\:\[alias\:a\]:hover {
alias: a;
}
@media (min-width: 640px) {
.hover\:sm\:\[alias\:a\]:hover {
alias: a;
}
}
@media (min-width: 768px) {
.hover\:md\:\[alias\:a\]:hover {
alias: a;
}
@media (prefers-color-scheme: dark) {
.hover\:md\:dark\:\[alias\:a\]:hover {
alias: a;
}
}
}
None of the above rules will be deduped. The tool reports the amount of dependent rules it initally finds and the amount of copies it makes to help see if this becomes a problem.
Modifiers that target anything but their own element, like marker
or *
, can't be aliased. Modifiers that target pseudo-elements are moved to the dependent rule's selector, so this limitation doesn't apply to them. However, it does negatively affect deduping.
<div class="before:md:hover:[alias:bmh] [bmh&]:dark:peer-invalid:font-bold ..."></div>
The tailwind generated css for the above markup is:
@media (min-width: 768px) {
.before\:md\:hover\:\[alias\:bmh\]:hover::before {
content: var(--tw-content);
alias: bmh;
}
}
@media (prefers-color-scheme: dark) {
bmh.peer:invalid ~ .\[bmh\&\]\:dark\:peer-invalid\:font-bold {
font-weight: 700;
}
}
Then, ::before
is cut from the outer and appended to the inner selector. The content
property is also moved:
@media (min-width: 768px) {
:is(.before\:md\:hover\:\[alias\:bmh\]:hover) {
@media (prefers-color-scheme: dark) {
.peer:invalid ~ &.\[bmh\&\]\:dark\:peer-invalid\:font-bold::before {
content: var(--tw-content);
font-weight: 700;
}
}
}
}
Modifiers that target children are allowed in dependent classes, but they make other modifiers apply to the target element and not to the children. For example, hover:[alias:a] [a&]:marker:text-sky-500
works the same as marker:hover:text-sky-500
and not as hover:marker:text-sky-500
.