Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Attempt] Exploring the Coexistence of Nuxt UI v3 and UnoCSS #3011

Open
byronogis opened this issue Jan 3, 2025 · 1 comment
Open

[Attempt] Exploring the Coexistence of Nuxt UI v3 and UnoCSS #3011

byronogis opened this issue Jan 3, 2025 · 1 comment
Labels
question Further information is requested

Comments

@byronogis
Copy link

byronogis commented Jan 3, 2025

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

For nuxt | vite

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.

Commits · byronogis/ui


@byronogis byronogis added the question Further information is requested label Jan 3, 2025
@byronogis
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant