@plutocms/supabase 0.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/nuxt/GENERATION.md +5 -0
- package/.agents/skills/nuxt/SKILL.md +55 -0
- package/.agents/skills/nuxt/references/advanced-hooks.md +289 -0
- package/.agents/skills/nuxt/references/advanced-layers.md +299 -0
- package/.agents/skills/nuxt/references/advanced-module-authoring.md +554 -0
- package/.agents/skills/nuxt/references/best-practices-data-fetching.md +357 -0
- package/.agents/skills/nuxt/references/best-practices-ssr.md +355 -0
- package/.agents/skills/nuxt/references/core-cli.md +263 -0
- package/.agents/skills/nuxt/references/core-config.md +162 -0
- package/.agents/skills/nuxt/references/core-data-fetching.md +236 -0
- package/.agents/skills/nuxt/references/core-deployment.md +224 -0
- package/.agents/skills/nuxt/references/core-directory-structure.md +269 -0
- package/.agents/skills/nuxt/references/core-modules.md +292 -0
- package/.agents/skills/nuxt/references/core-routing.md +226 -0
- package/.agents/skills/nuxt/references/features-components-autoimport.md +328 -0
- package/.agents/skills/nuxt/references/features-components.md +264 -0
- package/.agents/skills/nuxt/references/features-composables.md +276 -0
- package/.agents/skills/nuxt/references/features-server.md +265 -0
- package/.agents/skills/nuxt/references/features-state.md +194 -0
- package/.agents/skills/nuxt/references/rendering-modes.md +237 -0
- package/.agents/skills/nuxt-ui/SKILL.md +334 -0
- package/.agents/skills/nuxt-ui/references/components.md +377 -0
- package/.agents/skills/nuxt-ui/references/composables.md +127 -0
- package/.agents/skills/nuxt-ui/references/layouts/chat.md +195 -0
- package/.agents/skills/nuxt-ui/references/layouts/dashboard.md +220 -0
- package/.agents/skills/nuxt-ui/references/layouts/docs.md +141 -0
- package/.agents/skills/nuxt-ui/references/layouts/editor.md +167 -0
- package/.agents/skills/nuxt-ui/references/layouts/page.md +260 -0
- package/.agents/skills/nuxt-ui/references/theming.md +427 -0
- package/.agents/skills/supabase-postgres-best-practices/AGENTS.md +68 -0
- package/.agents/skills/supabase-postgres-best-practices/CLAUDE.md +1 -0
- package/.agents/skills/supabase-postgres-best-practices/README.md +116 -0
- package/.agents/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
- package/.editorconfig +12 -0
- package/.env.example +2 -0
- package/.eslintignore +1 -0
- package/.prettierrc +9 -0
- package/.vscode/extensions.json +10 -0
- package/.vscode/settings.json +61 -0
- package/README.md +62 -0
- package/app/components/navbar/NavbarAdmin.vue +169 -0
- package/app/composables/auth.ts +81 -0
- package/app/layouts/setup.vue +13 -0
- package/app/middleware/auth.ts +27 -0
- package/app/middleware/setup-check.ts +25 -0
- package/app/pages/admin/(auth)/login.vue +84 -0
- package/app/pages/admin/(auth)/signup.vue +128 -0
- package/app/pages/admin/settings.vue +116 -0
- package/app/pages/admin/setup.vue +300 -0
- package/app/pages/admin.vue +38 -0
- package/bun.lock +2685 -0
- package/eslint.config.mjs +88 -0
- package/nuxt.config.ts +55 -0
- package/package.json +44 -0
- package/public/schema.sql +103 -0
- package/scripts/supabase-typegen.ts +36 -0
- package/server/api/category/list.get.ts +9 -0
- package/server/api/category/new.post.ts +31 -0
- package/server/api/settings/first_setup.get.ts +39 -0
- package/server/api/settings/index.get.ts +27 -0
- package/server/api/settings/update.post.ts +36 -0
- package/server/api/setup/create.post.ts +58 -0
- package/server/api/signup.post.ts +28 -0
- package/server/api/users/[id].get.ts +21 -0
- package/server/api/users/index.get.ts +16 -0
- package/shared/types/supabase.ts +406 -0
- package/skills-lock.json +20 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Page Layout
|
|
2
|
+
|
|
3
|
+
Build public-facing pages — landing, blog, changelog, pricing — using the Header + Main + Footer shell with Page components.
|
|
4
|
+
|
|
5
|
+
## App shell
|
|
6
|
+
|
|
7
|
+
```vue [app.vue]
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import type { NavigationMenuItem } from '@nuxt/ui'
|
|
10
|
+
|
|
11
|
+
const items = computed<NavigationMenuItem[]>(() => [{
|
|
12
|
+
label: 'Features',
|
|
13
|
+
to: '#features'
|
|
14
|
+
}, {
|
|
15
|
+
label: 'Pricing',
|
|
16
|
+
to: '/pricing'
|
|
17
|
+
}, {
|
|
18
|
+
label: 'Blog',
|
|
19
|
+
to: '/blog'
|
|
20
|
+
}])
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<UApp>
|
|
25
|
+
<UHeader>
|
|
26
|
+
<template #title>
|
|
27
|
+
<Logo class="h-6 w-auto" />
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<UNavigationMenu :items="items" />
|
|
31
|
+
|
|
32
|
+
<template #right>
|
|
33
|
+
<UColorModeButton />
|
|
34
|
+
<UButton label="Sign in" color="neutral" variant="ghost" />
|
|
35
|
+
<UButton label="Get started" />
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<template #body>
|
|
39
|
+
<UNavigationMenu :items="items" orientation="vertical" class="-mx-2.5" />
|
|
40
|
+
</template>
|
|
41
|
+
</UHeader>
|
|
42
|
+
|
|
43
|
+
<UMain>
|
|
44
|
+
<NuxtPage />
|
|
45
|
+
</UMain>
|
|
46
|
+
|
|
47
|
+
<UFooter>
|
|
48
|
+
<template #left>
|
|
49
|
+
<p class="text-muted text-sm">Copyright © {{ new Date().getFullYear() }}</p>
|
|
50
|
+
</template>
|
|
51
|
+
<template #right>
|
|
52
|
+
<UButton icon="i-simple-icons-github" color="neutral" variant="ghost" to="https://github.com" target="_blank" />
|
|
53
|
+
</template>
|
|
54
|
+
</UFooter>
|
|
55
|
+
</UApp>
|
|
56
|
+
</template>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Landing page
|
|
60
|
+
|
|
61
|
+
```vue [pages/index.vue]
|
|
62
|
+
<template>
|
|
63
|
+
<UPageHero
|
|
64
|
+
title="Build faster with Nuxt UI"
|
|
65
|
+
description="A comprehensive Vue UI component library."
|
|
66
|
+
:links="[
|
|
67
|
+
{ label: 'Get started', to: '/docs', icon: 'i-lucide-square-play' },
|
|
68
|
+
{ label: 'Learn more', color: 'neutral', variant: 'subtle', trailingIcon: 'i-lucide-arrow-right' }
|
|
69
|
+
]"
|
|
70
|
+
orientation="horizontal"
|
|
71
|
+
>
|
|
72
|
+
<img src="/hero-image.png" alt="App screenshot" class="rounded-lg shadow-2xl ring ring-default" />
|
|
73
|
+
</UPageHero>
|
|
74
|
+
|
|
75
|
+
<UPageSection
|
|
76
|
+
id="features"
|
|
77
|
+
headline="Features"
|
|
78
|
+
title="Everything you need"
|
|
79
|
+
description="A comprehensive suite of components and utilities."
|
|
80
|
+
:features="[
|
|
81
|
+
{ title: 'Accessible', description: 'Built on Reka UI with full ARIA support.', icon: 'i-lucide-accessibility' },
|
|
82
|
+
{ title: 'Customizable', description: 'Tailwind Variants theming with full control.', icon: 'i-lucide-palette' },
|
|
83
|
+
{ title: 'Responsive', description: 'Mobile-first components.', icon: 'i-lucide-monitor-smartphone' }
|
|
84
|
+
]"
|
|
85
|
+
/>
|
|
86
|
+
|
|
87
|
+
<UPageCTA
|
|
88
|
+
title="Trusted by thousands of developers"
|
|
89
|
+
description="Join the community and start building today."
|
|
90
|
+
:links="[
|
|
91
|
+
{ label: 'Get started', color: 'neutral' },
|
|
92
|
+
{ label: 'Star on GitHub', color: 'neutral', variant: 'subtle', trailingIcon: 'i-lucide-arrow-right' }
|
|
93
|
+
]"
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
<UPageSection id="pricing" headline="Pricing" title="Simple, transparent pricing">
|
|
97
|
+
<UPricingPlans
|
|
98
|
+
:plans="[
|
|
99
|
+
{ title: 'Free', price: '$0', description: 'For personal projects', features: ['10 components', 'Community support'] },
|
|
100
|
+
{ title: 'Pro', price: '$99', description: 'For teams', features: ['All components', 'Priority support'], highlight: true },
|
|
101
|
+
{ title: 'Enterprise', price: 'Custom', description: 'For large teams', features: ['Custom components', 'Dedicated support'] }
|
|
102
|
+
]"
|
|
103
|
+
/>
|
|
104
|
+
</UPageSection>
|
|
105
|
+
</template>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Blog listing
|
|
109
|
+
|
|
110
|
+
```vue [pages/blog/index.vue]
|
|
111
|
+
<script setup lang="ts">
|
|
112
|
+
const { data: posts } = await useAsyncData('posts', () => queryCollection('posts').all())
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<template>
|
|
116
|
+
<UPage>
|
|
117
|
+
<UPageHero title="Blog" description="The latest news and updates from our team." />
|
|
118
|
+
|
|
119
|
+
<UPageBody>
|
|
120
|
+
<UContainer>
|
|
121
|
+
<UBlogPosts>
|
|
122
|
+
<UBlogPost
|
|
123
|
+
v-for="(post, index) in posts"
|
|
124
|
+
:key="index"
|
|
125
|
+
v-bind="post"
|
|
126
|
+
:to="post.path"
|
|
127
|
+
/>
|
|
128
|
+
</UBlogPosts>
|
|
129
|
+
</UContainer>
|
|
130
|
+
</UPageBody>
|
|
131
|
+
</UPage>
|
|
132
|
+
</template>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Blog article
|
|
136
|
+
|
|
137
|
+
```vue [pages/blog/[slug].vue]
|
|
138
|
+
<script setup lang="ts">
|
|
139
|
+
const route = useRoute()
|
|
140
|
+
|
|
141
|
+
const { data: post } = await useAsyncData(route.path, () => {
|
|
142
|
+
return queryCollection('posts').path(route.path).first()
|
|
143
|
+
})
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<template>
|
|
147
|
+
<UPage>
|
|
148
|
+
<UPageHeader :title="post.title" :description="post.description" />
|
|
149
|
+
|
|
150
|
+
<UPageBody>
|
|
151
|
+
<ContentRenderer :value="post" />
|
|
152
|
+
</UPageBody>
|
|
153
|
+
|
|
154
|
+
<template #right>
|
|
155
|
+
<UContentToc :links="post.body.toc.links" />
|
|
156
|
+
</template>
|
|
157
|
+
</UPage>
|
|
158
|
+
</template>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Changelog
|
|
162
|
+
|
|
163
|
+
```vue [pages/changelog.vue]
|
|
164
|
+
<script setup lang="ts">
|
|
165
|
+
const { data: versions } = await useAsyncData('versions', () => queryCollection('changelog').all())
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
<template>
|
|
169
|
+
<UPage>
|
|
170
|
+
<UPageHero title="Changelog" />
|
|
171
|
+
|
|
172
|
+
<UPageBody>
|
|
173
|
+
<UContainer>
|
|
174
|
+
<UChangelogVersions>
|
|
175
|
+
<UChangelogVersion v-for="(version, index) in versions" :key="index" v-bind="version" />
|
|
176
|
+
</UChangelogVersions>
|
|
177
|
+
</UContainer>
|
|
178
|
+
</UPageBody>
|
|
179
|
+
</UPage>
|
|
180
|
+
</template>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Key components
|
|
184
|
+
|
|
185
|
+
### Page sections
|
|
186
|
+
|
|
187
|
+
- `UPageHero` — Hero with title, description, links, and optional media (`orientation`: horizontal/vertical)
|
|
188
|
+
- `UPageSection` — Content section with headline, title, description, and `features` grid
|
|
189
|
+
- `UPageCTA` — Call to action block
|
|
190
|
+
- `UPageHeader` — Page title and description
|
|
191
|
+
- `UPageBody` — Main content area with prose styling
|
|
192
|
+
|
|
193
|
+
### Grids & cards
|
|
194
|
+
|
|
195
|
+
- `UPageGrid` / `UPageColumns` — Grid layouts
|
|
196
|
+
- `UPageCard` — Content card for grids
|
|
197
|
+
- `UPageFeature` — Individual feature item
|
|
198
|
+
- `UPageLogos` — Logo wall
|
|
199
|
+
|
|
200
|
+
### Blog & changelog
|
|
201
|
+
|
|
202
|
+
- `UBlogPosts` — Responsive grid of posts (`orientation`: horizontal/vertical)
|
|
203
|
+
- `UBlogPost` — Individual post card
|
|
204
|
+
- `UChangelogVersions` / `UChangelogVersion` — Changelog entries
|
|
205
|
+
|
|
206
|
+
### Pricing
|
|
207
|
+
|
|
208
|
+
- `UPricingPlans` — Pricing plan cards
|
|
209
|
+
- `UPricingTable` — Feature comparison table
|
|
210
|
+
|
|
211
|
+
### Footer
|
|
212
|
+
|
|
213
|
+
- `UFooterColumns` — Multi-column footer with link groups
|
|
214
|
+
|
|
215
|
+
## Variations
|
|
216
|
+
|
|
217
|
+
### Alternating sections
|
|
218
|
+
|
|
219
|
+
```vue
|
|
220
|
+
<UPageSection title="Feature A" orientation="horizontal">
|
|
221
|
+
<img src="/feature-a.png" />
|
|
222
|
+
</UPageSection>
|
|
223
|
+
|
|
224
|
+
<UPageSection title="Feature B" orientation="horizontal" reverse>
|
|
225
|
+
<img src="/feature-b.png" />
|
|
226
|
+
</UPageSection>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Feature grid
|
|
230
|
+
|
|
231
|
+
```vue
|
|
232
|
+
<UPageSection headline="Features" title="Why choose us">
|
|
233
|
+
<UPageGrid>
|
|
234
|
+
<UPageCard v-for="feature in features" :key="feature.title" v-bind="feature" />
|
|
235
|
+
</UPageGrid>
|
|
236
|
+
</UPageSection>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Blog with sidebar
|
|
240
|
+
|
|
241
|
+
```vue [layouts/blog.vue]
|
|
242
|
+
<template>
|
|
243
|
+
<UPage>
|
|
244
|
+
<template #left>
|
|
245
|
+
<UPageAside>
|
|
246
|
+
<UNavigationMenu
|
|
247
|
+
:items="[
|
|
248
|
+
{ label: 'All posts', to: '/blog', icon: 'i-lucide-newspaper' },
|
|
249
|
+
{ label: 'Tutorials', to: '/blog/tutorials', icon: 'i-lucide-graduation-cap' },
|
|
250
|
+
{ label: 'Announcements', to: '/blog/announcements', icon: 'i-lucide-megaphone' }
|
|
251
|
+
]"
|
|
252
|
+
orientation="vertical"
|
|
253
|
+
/>
|
|
254
|
+
</UPageAside>
|
|
255
|
+
</template>
|
|
256
|
+
|
|
257
|
+
<slot />
|
|
258
|
+
</UPage>
|
|
259
|
+
</template>
|
|
260
|
+
```
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Theming
|
|
2
|
+
|
|
3
|
+
## Semantic colors
|
|
4
|
+
|
|
5
|
+
| Color | Default | Purpose |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `primary` | green | CTAs, active states, brand |
|
|
8
|
+
| `secondary` | blue | Secondary actions |
|
|
9
|
+
| `success` | green | Success messages |
|
|
10
|
+
| `info` | blue | Informational |
|
|
11
|
+
| `warning` | yellow | Warnings |
|
|
12
|
+
| `error` | red | Errors, destructive actions |
|
|
13
|
+
| `neutral` | slate | Text, borders, disabled |
|
|
14
|
+
|
|
15
|
+
## Configuring colors
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// Nuxt — app.config.ts
|
|
19
|
+
export default defineAppConfig({
|
|
20
|
+
ui: {
|
|
21
|
+
colors: {
|
|
22
|
+
primary: 'indigo',
|
|
23
|
+
secondary: 'violet',
|
|
24
|
+
success: 'emerald',
|
|
25
|
+
error: 'rose',
|
|
26
|
+
neutral: 'zinc'
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// Vue — vite.config.ts
|
|
34
|
+
ui({
|
|
35
|
+
ui: {
|
|
36
|
+
colors: { primary: 'indigo', secondary: 'violet', neutral: 'zinc' }
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You can only use colors that exist in your theme — either [Tailwind's default colors](https://tailwindcss.com/docs/colors) or custom colors defined with `@theme`.
|
|
42
|
+
|
|
43
|
+
## Adding custom colors
|
|
44
|
+
|
|
45
|
+
1. Define all 11 shades in CSS:
|
|
46
|
+
|
|
47
|
+
```css
|
|
48
|
+
/* assets/css/main.css */
|
|
49
|
+
@theme static {
|
|
50
|
+
--color-brand-50: #fef2f2;
|
|
51
|
+
--color-brand-100: #fee2e2;
|
|
52
|
+
--color-brand-200: #fecaca;
|
|
53
|
+
--color-brand-300: #fca5a5;
|
|
54
|
+
--color-brand-400: #f87171;
|
|
55
|
+
--color-brand-500: #ef4444;
|
|
56
|
+
--color-brand-600: #dc2626;
|
|
57
|
+
--color-brand-700: #b91c1c;
|
|
58
|
+
--color-brand-800: #991b1b;
|
|
59
|
+
--color-brand-900: #7f1d1d;
|
|
60
|
+
--color-brand-950: #450a0a;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
2. Assign it as a semantic color value: `ui: { colors: { primary: 'brand' } }`
|
|
65
|
+
|
|
66
|
+
You can only use colors that have all shades defined — either from Tailwind's defaults or custom `@theme` definitions.
|
|
67
|
+
|
|
68
|
+
### Extending with new semantic color names
|
|
69
|
+
|
|
70
|
+
If you need a new semantic color beyond the defaults (e.g., `tertiary`), register it in `theme.colors`:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// Nuxt — nuxt.config.ts
|
|
74
|
+
export default defineNuxtConfig({
|
|
75
|
+
ui: {
|
|
76
|
+
theme: {
|
|
77
|
+
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// Vue — vite.config.ts
|
|
85
|
+
ui({
|
|
86
|
+
theme: {
|
|
87
|
+
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then assign it: `ui: { colors: { tertiary: 'indigo' } }` and use it via the `color` prop: `<UButton color="tertiary">`.
|
|
93
|
+
|
|
94
|
+
## CSS utilities
|
|
95
|
+
|
|
96
|
+
### Text
|
|
97
|
+
|
|
98
|
+
| Class | Use | Light value | Dark value |
|
|
99
|
+
|---|---|---|---|
|
|
100
|
+
| `text-default` | Body text | `neutral-700` | `neutral-200` |
|
|
101
|
+
| `text-muted` | Secondary text | `neutral-500` | `neutral-400` |
|
|
102
|
+
| `text-dimmed` | Placeholders, hints | `neutral-400` | `neutral-500` |
|
|
103
|
+
| `text-toned` | Subtitles | `neutral-600` | `neutral-300` |
|
|
104
|
+
| `text-highlighted` | Headings, emphasis | `neutral-900` | `white` |
|
|
105
|
+
| `text-inverted` | On dark/light backgrounds | `white` | `neutral-900` |
|
|
106
|
+
|
|
107
|
+
### Background
|
|
108
|
+
|
|
109
|
+
| Class | Use | Light value | Dark value |
|
|
110
|
+
|---|---|---|---|
|
|
111
|
+
| `bg-default` | Page background | `white` | `neutral-900` |
|
|
112
|
+
| `bg-muted` | Subtle sections | `neutral-50` | `neutral-800` |
|
|
113
|
+
| `bg-elevated` | Cards, modals | `neutral-100` | `neutral-800` |
|
|
114
|
+
| `bg-accented` | Hover states | `neutral-200` | `neutral-700` |
|
|
115
|
+
| `bg-inverted` | Inverted sections | `neutral-900` | `white` |
|
|
116
|
+
|
|
117
|
+
### Border
|
|
118
|
+
|
|
119
|
+
| Class | Use | Light value | Dark value |
|
|
120
|
+
|---|---|---|---|
|
|
121
|
+
| `border-default` | Default borders | `neutral-200` | `neutral-800` |
|
|
122
|
+
| `border-muted` | Subtle borders | `neutral-200` | `neutral-700` |
|
|
123
|
+
| `border-accented` | Emphasized borders | `neutral-300` | `neutral-700` |
|
|
124
|
+
| `border-inverted` | Inverted borders | `neutral-900` | `white` |
|
|
125
|
+
|
|
126
|
+
### Semantic color utilities
|
|
127
|
+
|
|
128
|
+
Each semantic color (`primary`, `secondary`, `success`, `info`, `warning`, `error`) is available as a Tailwind utility: `text-primary`, `bg-primary`, `border-primary`, `ring-primary`, etc.
|
|
129
|
+
|
|
130
|
+
They resolve to shade **500** in light mode and shade **400** in dark mode (via `--ui-<color>` CSS variables). This is generated at runtime by the colors plugin — you don't need to write dark-mode variants manually.
|
|
131
|
+
|
|
132
|
+
To adjust which shade is used, override `--ui-primary` (or any semantic color) in your `main.css`:
|
|
133
|
+
|
|
134
|
+
```css
|
|
135
|
+
:root { --ui-primary: var(--ui-color-primary-600); }
|
|
136
|
+
.dark { --ui-primary: var(--ui-color-primary-300); }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### CSS variables
|
|
140
|
+
|
|
141
|
+
All customizable in `main.css`:
|
|
142
|
+
|
|
143
|
+
```css
|
|
144
|
+
:root {
|
|
145
|
+
--ui-radius: 0.25rem; /* base radius for all components */
|
|
146
|
+
--ui-container: 80rem; /* UContainer max-width */
|
|
147
|
+
--ui-header-height: 4rem; /* UHeader height */
|
|
148
|
+
--ui-primary: var(--ui-color-primary-500); /* adjust shade used */
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.dark {
|
|
152
|
+
--ui-primary: var(--ui-color-primary-400);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Solid colors (black/white)
|
|
157
|
+
|
|
158
|
+
```css
|
|
159
|
+
:root { --ui-primary: black; }
|
|
160
|
+
.dark { --ui-primary: white; }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Component theme customization
|
|
164
|
+
|
|
165
|
+
### How it works
|
|
166
|
+
|
|
167
|
+
Components are styled with [Tailwind Variants](https://www.tailwind-variants.org/). The theme defines:
|
|
168
|
+
|
|
169
|
+
- **`slots`** — named style targets (e.g., `root`, `base`, `label`, `leadingIcon`)
|
|
170
|
+
- **`variants`** — styles applied based on props (e.g., `color`, `variant`, `size`)
|
|
171
|
+
- **`compoundVariants`** — styles for specific prop combinations (e.g., `color: 'primary'` + `variant: 'outline'`)
|
|
172
|
+
- **`defaultVariants`** — default prop values when none are specified
|
|
173
|
+
|
|
174
|
+
### Override priority
|
|
175
|
+
|
|
176
|
+
**`ui` prop / `class` prop > global config > theme defaults**
|
|
177
|
+
|
|
178
|
+
The `ui` prop overrides slots **after** variants are computed. If the `size: 'md'` variant applies `size-5` to `trailingIcon`, and you set `:ui="{ trailingIcon: 'size-3' }"`, the `size-3` wins.
|
|
179
|
+
|
|
180
|
+
Tailwind Variants uses [tailwind-merge](https://github.com/dcastil/tailwind-merge) under the hood so conflicting classes are resolved automatically.
|
|
181
|
+
|
|
182
|
+
### Understanding the generated theme
|
|
183
|
+
|
|
184
|
+
Every component's full resolved theme is generated at build time. Always read this file before customizing a component — it shows exactly what classes are applied where.
|
|
185
|
+
|
|
186
|
+
- **Nuxt**: `.nuxt/ui/<component>.ts`
|
|
187
|
+
- **Vue**: `node_modules/.nuxt-ui/ui/<component>.ts`
|
|
188
|
+
|
|
189
|
+
For example, the card theme:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
{
|
|
193
|
+
slots: {
|
|
194
|
+
root: "rounded-lg overflow-hidden",
|
|
195
|
+
header: "p-4 sm:px-6",
|
|
196
|
+
body: "p-4 sm:p-6",
|
|
197
|
+
footer: "p-4 sm:px-6"
|
|
198
|
+
},
|
|
199
|
+
variants: {
|
|
200
|
+
variant: {
|
|
201
|
+
outline: { root: "bg-default ring ring-default divide-y divide-default" },
|
|
202
|
+
soft: { root: "bg-elevated/50 divide-y divide-default" }
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
defaultVariants: { variant: "outline" }
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Global config
|
|
210
|
+
|
|
211
|
+
Override the theme for all instances of a component:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
// Nuxt — app.config.ts
|
|
215
|
+
export default defineAppConfig({
|
|
216
|
+
ui: {
|
|
217
|
+
button: {
|
|
218
|
+
slots: {
|
|
219
|
+
base: 'font-bold rounded-full'
|
|
220
|
+
},
|
|
221
|
+
variants: {
|
|
222
|
+
size: {
|
|
223
|
+
md: { leadingIcon: 'size-4' }
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
compoundVariants: [{
|
|
227
|
+
color: 'neutral',
|
|
228
|
+
variant: 'outline',
|
|
229
|
+
class: { base: 'ring-2' }
|
|
230
|
+
}],
|
|
231
|
+
defaultVariants: {
|
|
232
|
+
color: 'neutral',
|
|
233
|
+
variant: 'outline'
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
// Vue — vite.config.ts
|
|
242
|
+
ui({
|
|
243
|
+
ui: {
|
|
244
|
+
button: {
|
|
245
|
+
slots: { base: 'font-bold rounded-full' },
|
|
246
|
+
defaultVariants: { color: 'neutral', variant: 'outline' }
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Per-instance (`ui` prop)
|
|
253
|
+
|
|
254
|
+
Overrides slots after variant computation:
|
|
255
|
+
|
|
256
|
+
```vue
|
|
257
|
+
<UButton :ui="{ base: 'font-mono', trailingIcon: 'size-3 rotate-90' }" />
|
|
258
|
+
<UCard :ui="{ root: 'shadow-xl', body: 'p-8' }" />
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Per-instance (`class` prop)
|
|
262
|
+
|
|
263
|
+
Overrides the `root` or `base` slot:
|
|
264
|
+
|
|
265
|
+
```vue
|
|
266
|
+
<UButton class="rounded-none">Square</UButton>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Components without slots (e.g., `UContainer`, `USkeleton`, `UMain`) only have the `class` prop.
|
|
270
|
+
|
|
271
|
+
### Theme structure patterns
|
|
272
|
+
|
|
273
|
+
**Slots-based** (most components — `slots` is an object in the generated theme):
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
// global config
|
|
277
|
+
ui: {
|
|
278
|
+
button: {
|
|
279
|
+
slots: { base: 'font-bold' }
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// per instance
|
|
283
|
+
<UButton :ui="{ base: 'font-bold' }" />
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Flat base** (`base` is a top-level string in the generated theme):
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
// global config
|
|
290
|
+
ui: {
|
|
291
|
+
container: {
|
|
292
|
+
base: 'max-w-lg'
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// per instance — class prop only
|
|
296
|
+
<UContainer class="max-w-lg" />
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Always check the generated theme file to see which pattern applies.
|
|
300
|
+
|
|
301
|
+
## Dark mode
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
const colorMode = useColorMode()
|
|
305
|
+
colorMode.preference = 'dark' // 'light', 'dark', 'system'
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
```vue
|
|
309
|
+
<UColorModeButton /> <!-- Toggle -->
|
|
310
|
+
<UColorModeSelect /> <!-- Dropdown -->
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Fonts
|
|
314
|
+
|
|
315
|
+
```css
|
|
316
|
+
/* assets/css/main.css */
|
|
317
|
+
@theme {
|
|
318
|
+
--font-sans: 'Public Sans', system-ui, sans-serif;
|
|
319
|
+
--font-mono: 'JetBrains Mono', monospace;
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
In Nuxt, fonts defined with `@theme` are automatically loaded by the `@nuxt/fonts` module.
|
|
324
|
+
|
|
325
|
+
## Brand customization playbook
|
|
326
|
+
|
|
327
|
+
Follow these steps to fully rebrand Nuxt UI (e.g., "make a Ghibli theme", "match our corporate brand"):
|
|
328
|
+
|
|
329
|
+
### Step 1 — Define the color palette
|
|
330
|
+
|
|
331
|
+
Pick colors that match the brand. Map them to semantic roles:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
// app.config.ts (Nuxt) or vite.config.ts (Vue)
|
|
335
|
+
ui: {
|
|
336
|
+
colors: {
|
|
337
|
+
primary: 'emerald', // brand accent
|
|
338
|
+
secondary: 'amber', // secondary accent
|
|
339
|
+
success: 'green',
|
|
340
|
+
info: 'sky',
|
|
341
|
+
warning: 'orange',
|
|
342
|
+
error: 'rose',
|
|
343
|
+
neutral: 'stone' // affects all text, borders, backgrounds
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
If no Tailwind default color fits, define custom shades in CSS (see [Adding custom colors](#adding-custom-colors)):
|
|
349
|
+
|
|
350
|
+
```css
|
|
351
|
+
@theme static {
|
|
352
|
+
--color-forest-50: #f0fdf4;
|
|
353
|
+
/* ... all 11 shades (50–950) ... */
|
|
354
|
+
--color-forest-950: #052e16;
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Then use it: `primary: 'forest'`.
|
|
359
|
+
|
|
360
|
+
### Step 2 — Set fonts
|
|
361
|
+
|
|
362
|
+
```css
|
|
363
|
+
/* assets/css/main.css */
|
|
364
|
+
@theme {
|
|
365
|
+
--font-sans: 'Quicksand', system-ui, sans-serif;
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Step 3 — Adjust CSS variables
|
|
370
|
+
|
|
371
|
+
```css
|
|
372
|
+
:root {
|
|
373
|
+
--ui-radius: 0.75rem; /* rounder = softer/playful, smaller = sharper/corporate */
|
|
374
|
+
--ui-primary: var(--ui-color-primary-600); /* adjust which shade is used */
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.dark {
|
|
378
|
+
--ui-primary: var(--ui-color-primary-400);
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Step 4 — Override key components globally
|
|
383
|
+
|
|
384
|
+
Read the generated theme files to find slot names, then apply global overrides:
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
// app.config.ts (Nuxt) or vite.config.ts (Vue)
|
|
388
|
+
ui: {
|
|
389
|
+
// ... colors from Step 1
|
|
390
|
+
button: {
|
|
391
|
+
slots: {
|
|
392
|
+
base: 'rounded-full font-semibold'
|
|
393
|
+
},
|
|
394
|
+
defaultVariants: {
|
|
395
|
+
variant: 'soft'
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
card: {
|
|
399
|
+
slots: {
|
|
400
|
+
root: 'rounded-2xl shadow-lg'
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
badge: {
|
|
404
|
+
slots: {
|
|
405
|
+
base: 'rounded-full'
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
> **Tip**: Read `.nuxt/ui/button.ts` (Nuxt) or `node_modules/.nuxt-ui/ui/button.ts` (Vue) to see all available slots and variants before overriding.
|
|
412
|
+
|
|
413
|
+
### Step 5 — Verify dark mode
|
|
414
|
+
|
|
415
|
+
Check that both modes look correct. Adjust `--ui-primary` shade per mode and test contrast. Use `useColorMode()` to toggle during development.
|
|
416
|
+
|
|
417
|
+
### Quick checklist
|
|
418
|
+
|
|
419
|
+
| Step | What to change | Where |
|
|
420
|
+
|---|---|---|
|
|
421
|
+
| Colors | `primary`, `secondary`, `neutral` | `app.config.ts` / `vite.config.ts` |
|
|
422
|
+
| Custom palette | 11 shades per color | `main.css` (`@theme static`) |
|
|
423
|
+
| Fonts | `--font-sans`, `--font-mono` | `main.css` (`@theme`) |
|
|
424
|
+
| Radius | `--ui-radius` | `main.css` (`:root`) |
|
|
425
|
+
| Primary shade | `--ui-primary` | `main.css` (`:root` + `.dark`) |
|
|
426
|
+
| Component shapes | Global slot overrides | `app.config.ts` / `vite.config.ts` |
|
|
427
|
+
| Dark mode | Verify contrast, adjust variables | `main.css` (`.dark`) |
|