Skip to content

feat(module): generate tailwindcss theme colors #2967

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

Merged
merged 10 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href]

We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Tailwind CSS v4](https://tailwindcss.com/docs/v4-beta), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Tailwind CSS v4](https://tailwindcss.com/), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.

> [!NOTE]
> You are on the `v3` development branch, check out the [dev branch](https://github.com/nuxt/ui/tree/dev) for Nuxt UI v2.
Expand Down
3 changes: 2 additions & 1 deletion docs/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export default defineAppConfig({
duration: 5000
},
theme: {
radius: 0.25
radius: 0.25,
blackAsPrimary: false
},
ui: {
colors: {
Expand Down
4 changes: 3 additions & 1 deletion docs/app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const links = computed(() => [{

const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')

useHead({
meta: [
Expand All @@ -61,7 +62,8 @@ useHead({
{ rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` }
],
style: [
{ innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 }
{ innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 },
{ innerHTML: blackAsPrimary, id: 'nuxt-ui-black-as-primary', tagPriority: -2 }
],
htmlAttrs: {
lang: 'en'
Expand Down
130 changes: 72 additions & 58 deletions docs/app/components/theme-picker/ThemePicker.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,64 @@
<script setup lang="ts">
import colors from 'tailwindcss/colors'
import { omit } from '#ui/utils'

const appConfig = useAppConfig()
const colorMode = useColorMode()

const neutralColors = ['slate', 'gray', 'zinc', 'neutral', 'stone']
const neutral = computed({
get() {
return appConfig.ui.colors.neutral
},
set(option) {
appConfig.ui.colors.neutral = option
window.localStorage.setItem('nuxt-ui-neutral', appConfig.ui.colors.neutral)
}
})

const colorsToOmit = ['inherit', 'current', 'transparent', 'black', 'white', ...neutralColors]
const primaryColors = Object.keys(omit(colors, colorsToOmit as any))
const primary = computed({
get() {
return appConfig.ui.colors.primary
},
set(option) {
appConfig.ui.colors.primary = option
window.localStorage.setItem('nuxt-ui-primary', appConfig.ui.colors.primary)
setBlackAsPrimary(false)
}
})

const radiuses = [0, 0.125, 0.25, 0.375, 0.5]
const radius = computed({
get() {
return appConfig.theme.radius
},
set(option) {
appConfig.theme.radius = option
window.localStorage.setItem('nuxt-ui-radius', String(appConfig.theme.radius))
}
})

const modes = [
{ label: 'light', icon: appConfig.ui.icons.light },
{ label: 'dark', icon: appConfig.ui.icons.dark }
]
const mode = computed({
get() {
return colorMode.value
},
set(option) {
colorMode.preference = option
}
})

function setBlackAsPrimary(value: boolean) {
appConfig.theme.blackAsPrimary = value
window.localStorage.setItem('nuxt-ui-black-as-primary', String(value))
}
</script>

<template>
<UPopover :ui="{ content: 'w-72 px-6 py-4 flex flex-col gap-4' }">
<template #default="{ open }">
Expand All @@ -18,12 +79,22 @@
</legend>

<div class="grid grid-cols-3 gap-1 -mx-2">
<ThemePickerButton
chip="primary"
label="Black"
:selected="appConfig.theme.blackAsPrimary"
@click="setBlackAsPrimary(true)"
>
<template #leading>
<span class="inline-block w-2 h-2 rounded-full bg-black dark:bg-white" />
</template>
</ThemePickerButton>
<ThemePickerButton
v-for="color in primaryColors"
:key="color"
:label="color"
:chip="color"
:selected="primary === color"
:selected="!appConfig.theme.blackAsPrimary && primary === color"
@click="primary = color"
/>
</div>
Expand Down Expand Up @@ -81,60 +152,3 @@
</template>
</UPopover>
</template>

<script setup lang="ts">
import colors from 'tailwindcss/colors'
import { omit } from '#ui/utils'

const appConfig = useAppConfig()
const colorMode = useColorMode()

// Computed

const neutralColors = ['slate', 'gray', 'zinc', 'neutral', 'stone']
const neutral = computed({
get() {
return appConfig.ui.colors.neutral
},
set(option) {
appConfig.ui.colors.neutral = option
window.localStorage.setItem('nuxt-ui-neutral', appConfig.ui.colors.neutral)
}
})

const colorsToOmit = ['inherit', 'current', 'transparent', 'black', 'white', ...neutralColors]
const primaryColors = Object.keys(omit(colors, colorsToOmit as any))
const primary = computed({
get() {
return appConfig.ui.colors.primary
},
set(option) {
appConfig.ui.colors.primary = option
window.localStorage.setItem('nuxt-ui-primary', appConfig.ui.colors.primary)
}
})

const radiuses = [0, 0.125, 0.25, 0.375, 0.5]
const radius = computed({
get() {
return appConfig.theme.radius
},
set(option) {
appConfig.theme.radius = option
window.localStorage.setItem('nuxt-ui-radius', String(appConfig.theme.radius))
}
})

const modes = [
{ label: 'light', icon: appConfig.ui.icons.light },
{ label: 'dark', icon: appConfig.ui.icons.dark }
]
const mode = computed({
get() {
return colorMode.value
},
set(option) {
colorMode.preference = option
}
})
</script>
36 changes: 19 additions & 17 deletions docs/app/components/theme-picker/ThemePickerButton.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
<script setup lang="ts">
defineProps<{
label: string
icon?: string
chip?: string
selected?: boolean
}>()
</script>

<template>
<UButton
size="sm"
Expand All @@ -8,23 +17,16 @@
class="capitalize ring-[var(--ui-border)] rounded-[var(--ui-radius)] text-[11px]"
>
<template v-if="chip" #leading>
<span
class="inline-block w-2 h-2 rounded-full"
:class="`bg-[var(--color-light)] dark:bg-[var(--color-dark)]`"
:style="{
'--color-light': `var(--color-${chip}-500)`,
'--color-dark': `var(--color-${chip}-400)`
}"
/>
<slot name="leading">
<span
class="inline-block w-2 h-2 rounded-full"
:class="`bg-[var(--color-light)] dark:bg-[var(--color-dark)]`"
:style="{
'--color-light': `var(--color-${chip}-500)`,
'--color-dark': `var(--color-${chip}-400)`
}"
/>
</slot>
</template>
</UButton>
</template>

<script setup lang="ts">
defineProps<{
label: string
icon?: string
chip?: string
selected?: boolean
}>()
</script>
26 changes: 22 additions & 4 deletions docs/app/plugins/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ export default defineNuxtPlugin({
}
}

function updateBlackAsPrimary() {
const blackAsPrimary = localStorage.getItem('nuxt-ui-black-as-primary')
if (blackAsPrimary) {
appConfig.theme.blackAsPrimary = blackAsPrimary === 'true'
}
}

updateColor('primary')
updateColor('neutral')
updateRadius()
updateBlackAsPrimary()
}

if (import.meta.server) {
Expand All @@ -31,10 +39,12 @@ export default defineNuxtPlugin({

if (localStorage.getItem('nuxt-ui-primary')) {
const primaryColor = localStorage.getItem('nuxt-ui-primary');
html = html.replace(
/(--ui-color-primary-\\d{2,3}:\\s*var\\()--color-${appConfig.ui.colors.primary}-(\\d{2,3}\\))/g,
\`$1--color-\${primaryColor}-$2\`
);
if (primaryColor !== 'black') {
html = html.replace(
/(--ui-color-primary-\\d{2,3}:\\s*var\\()--color-${appConfig.ui.colors.primary}-(\\d{2,3}\\))/g,
\`$1--color-\${primaryColor}-$2\`
);
}
}
if (localStorage.getItem('nuxt-ui-neutral')) {
const neutralColor = localStorage.getItem('nuxt-ui-neutral');
Expand All @@ -56,6 +66,14 @@ export default defineNuxtPlugin({
`.replace(/\s+/g, ' '),
type: 'text/javascript',
tagPriority: -1
}, {
innerHTML: `
if (localStorage.getItem('nuxt-ui-black-as-primary') === 'true') {
document.querySelector('style#nuxt-ui-black-as-primary').innerHTML = ':root { --ui-primary: black; } .dark { --ui-primary: white; }';
} else {
document.querySelector('style#nuxt-ui-black-as-primary').innerHTML = '';
}
`.replace(/\s+/g, ' ')
}]
})
}
Expand Down
6 changes: 3 additions & 3 deletions docs/content/1.getting-started/1.index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ This transition empowers Nuxt UI to become a more comprehensive and flexible UI

### Tailwind CSS v4

Nuxt UI v3 integrates the latest Tailwind CSS v4 beta (released Nov 21, 2024), bringing significant improvements:
Nuxt UI v3 integrates the latest Tailwind CSS v4, bringing significant improvements:

- **Built for performance**: Full builds in the new engine are up to 5x faster, and incremental builds are over 100x faster β€” and measured in microseconds.
- **Unified toolchain**: Built-in import handling, vendor prefixing, and syntax transforms, with no additional tooling required.
- **CSS-first configuration**: A reimagined developer experience where you customize and extend the framework directly in CSS instead of a JavaScript configuration file.
- **Designed for the modern web**: Built on native cascade layers, wide-gamut colors, and including first-class support for modern CSS features like container queries, @starting-style, popovers, and more.

::note{to="https://tailwindcss.com/docs/v4-beta" target="_blank" aria-label="Tailwind CSS v4 beta documentation"}
For a comprehensive overview of Tailwind CSS v4 beta features, read the **prerelease documentation**.
::note{to="https://tailwindcss.com/docs/upgrade-guide" target="_blank" aria-label="Tailwind CSS v4 upgrade guide"}
Learn how to upgrade your project from Tailwind CSS v3 to v4.
::

### Tailwind Variants
Expand Down
Loading