-
Notifications
You must be signed in to change notification settings - Fork 805
Description
Related #196
Alias | Name | Version |
---|---|---|
ui | nuxt/ui | v3.0.0-alpha.10 |
tw | tailwindcss | v4.0.0-beta.8 |
tv | tailwind-variants | v0.3.0 |
tm | tailwind-merge | v2.6.0 |
uno | unocss | v0.65.3 |
ui internally uses tv to flexibly generate component class names. tv internally uses tm to merge classes with the same effect (e.g., p-2 p-4
--> p-4
), and finally uses tw to recognize class names and generate style classes.
After examining tv and tm, it became apparent that they don't strongly depend on tw, which opens up the possibility of replacing the final style generation step with uno.
## tw Prefix Attempt (Optional)
Initially, the idea was to limit tw to ui internals and uno for user usage. With tw's prefix feature, the class rules became simpler. However, ui internally (v3.0.0-alpha.10) doesn't yet support passing tw prefix configuration, so let's first make ui support tw prefixes.
Understanding tv Variants
To add prefixes, we need to analyze the data structure that tv accepts.
Regular Variants
import { tv } from 'tailwind-variants';
tv({
variants: {
foo: {
foo_val: 'px-2', // or ['pxx-2', ...], be the same, class / className
},
}
})({ foo: 'foo_val' })
Boolean Variants (Including true/false keys)
tv({
variants: {
foo: {
true: 'px-2',
false: 'py-2',
}
}
})({ foo: true, })
Compound Variants (Intersection between variants)
tv({
variants: {
foo: {
foo_val: 'px-2',
foo_val2: 'px-2',
},
bar: {
true: 'px-2',
false: 'py-2',
},
},
compoundVariants: [
{
foo: 'foo_val', // or ['foo_val', 'foo_val2', ]
bar: true,
class: 'text-white', // You can also use "className" as the key
}
],
})({ foo: true, })
Responsive Variants (Device screen sizes)
tv({
variants: {
foo: {
foo_val: 'px-2',
foo_val2: 'px-4',
},
}
},
{
responsiveVariants: ['sm', 'md'] // or `true` for all
})({
foo: {
initial: 'foo_val',
sm: 'foo_val',
md: 'foo_val2',
}
})
Slot Variants (Combining above variants with slots)
tv({
slots: {
slot_a: 'mx-2',
slot_b: 'mx-2',
},
compoundSlots: [
{
slots: ['slot_a', 'slot_b', ],
class: '',
foo: 'foo_val', // for all slot while variant is missing
},
],
variants: {
foo: {
foo_val: {
slot_a: 'px-2',
}
},
bar: {
true: {
slot_a: 'mx-2'
},
false: {
slot_a: 'mx-0'
},
},
},
compoundVariants: [
{
foo: 'foo_val',
bar: true,
class: {
slot_a: '',
}
}
],
{
responsiveVariants: ['sm', 'md'] // or `true` for all
}
})({
foo: {
initial: 'foo_val',
sm: 'foo_val',
md: 'foo_val2',
}
}) // --> { foo_slot, bar_slot, }
Summary
After adding prefix functionality locally (#3009 ) and configuring tw prefix (while installing uno to handle non-prefix classes in the playground for page styling), practical usage revealed some unavoidable conflicts, such as at-rule usage. Additionally, this approach doesn't effectively prevent files from being processed twice (by both tw & uno). Therefore, let's try a different approach: remove tw completely and use UnoCSS for everything!
Modifying ui
Disabling tw Plugin
Remove @tailwindcss/vite
installation handling from ui internals.
Remove tw imports and specific configurations from ui playground.
Introducing uno
Also, Style Reset can be applied as needed.
IMPORTANT: Include @nuxt/ui files
export default defineConfig({
presets: [
presetUno(),
],
content: {
pipeline: {
include: [
// the default
/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/,
// IMPORTANT include @nuxt/ui files
/\.nuxt\/ui\//,
]
}
}
})
Primitive Variables
uno doesn't generate primitive variables like tw does (e.g., --color-green-500
, --text-lg
, --radius-md
, etc.), but ui internally needs these variables.
For now, we'll directly copy the primitive variables from tw's normal loaded to local.
Rule Inconsistencies
There are must more undiscovered issues
Ring
In uno, the default ring
width is 3px, different from tw's 1px.
Solution:
shortcuts: {
ring: 'ring-1'
},
Color Opacity
tw internally uses color-mix, allowing direct opacity settings for color variables.
Currently, uno doesn't support this directly.
tw: class="bg-[var(--color-primary)]/20"
✓
uno: class="bg-[var(--color-primary)]/20"
✕
uno: class="bg-[rgb(255,5,5)]/20"
✓
:root {
--color-primary: rgb(255, 5, 5);
}
After observing ui usage, uno rules were added:
uno: class="bg-[var(--ui-primaey)]/20"
✓
uno: class="hover:bg-[var(--ui-primaey)]/75"
✓
Solution:
rules: [
[/(?:([^:\s]+):)?bg-.*?\[(var\(--[^-]+-[^)]+\))\]\/(\d+)/, function* ([, modifier, color, alpha], { symbols }) {
yield {
background: `color-mix(in oklab, ${color} ${alpha}%, transparent)`
}
if (modifier) {
yield {
[symbols.selector]: selector => `${selector}:${modifier}`,
background: `color-mix(in oklab, ${color} ${alpha}%, transparent)`
}
}
}],
[/(?:([^:\s]+):)?text-.*?\[(var\(--[^-]+-[^)]+\))\]\/(\d+)/, function* ([, modifier, color, alpha], { symbols }) {
yield {
color: `color-mix(in oklab, ${color} ${alpha}%, transparent)`
}
if (modifier) {
yield {
[symbols.selector]: selector => `${selector}:${modifier}`,
color: `color-mix(in oklab, ${color} ${alpha}%, transparent)`
}
}
}]
],
Now, let's run the playground! ~
Notes
tm's handling of tw and uno exclusive rules yields unexpected results
tw doesn't directly support units (px, em, ...) like text-Xpx
, requiring text-[Xpx]
instead.
text-md text-[20px]
-->text-[20px]
text-md text-20px
-->text-md text-20px
text-16px text-20px
-->text-20px
text-[16px] text-[20px]
-->text-[20px]
When using ui, it's recommended to pass classes that tw can also parse.