App UI Design
This skill guides UI/UX in generated app features (Fusebase Apps). Use shadcn/ui for all UI. Apply a clear design direction and avoid generic AI aesthetics.
Design philosophy
Clarity over decoration. Every visual choice supports hierarchy and scannability. Prefer one bold aesthetic direction (minimal, warm, editorial, etc.) and execute it consistently.
- Purpose first: What does the screen do? Who uses it?
- Tone: Choose one direction (e.g. minimal, warm/SaaS, editorial, utilitarian) and stick to it.
- Differentiation: Avoid default "AI" look: no Inter/Roboto-only, no purple gradients on white, no same-as-everyone layouts. Vary fonts, palette, and density per context.
Visual identity
Tailwind CSS v4
Features use Tailwind CSS v4 (via @tailwindcss/postcss). Key differences from v3:
- Import: Use
@import "tailwindcss" in globals.css (not @tailwind base/components/utilities).
- No
tailwind.config.js: Configuration is CSS-first. Use @theme in CSS to define custom tokens.
- Content detection is automatic: Tailwind v4 scans project files automatically. If needed, use
@source "../path/**/*.tsx" to add extra directories.
⚠️ CRITICAL — CSS variables in arbitrary values:
Do NOT define raw :root CSS variables and reference them via arbitrary value syntax. This is the most common Tailwind v4 pitfall:
/* ❌ BROKEN — variables are NOT available to Tailwind's arbitrary value resolver */
:root {
--card: #ffffff;
--foreground: #0f172a;
}
// ❌ BROKEN — these classes compile but resolve to empty/broken values
className="bg-[var(--card)] text-[var(--foreground)]"
Instead, use one of these approaches:
Use Tailwind's built-in color palette (preferred for most features):
// ✅ Works — uses Tailwind's first-class utility classes
className="bg-white text-slate-900 border-slate-200"
className="text-indigo-500 bg-indigo-50"
Register custom tokens via @theme (only if you need custom colors):
@import "tailwindcss";
@theme {
--color-brand: #6366f1;
--color-surface: #ffffff;
}
// ✅ Works — registered via @theme
className="bg-surface text-brand"
Use @apply for reusable component classes (buttons, inputs):
.btn-primary {
@apply inline-flex items-center gap-2 px-5 py-2.5 text-sm font-semibold text-white rounded-xl;
background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
}
Use inline style only for truly dynamic values (e.g. colors from API data):
// ✅ OK — color is runtime data, can't be a utility class
style={{ color: product.categoryColor, backgroundColor: `${product.categoryColor}18` }}
shadcn/ui (when used)
- shadcn/ui components live in
components/ui/ (copied into the project, not installed as a package). Import from @/components/ui/....
- When shadcn/ui is set up, its CSS variables (
--background, --foreground, etc.) are registered through its own theme system and work with utility classes like bg-background, text-foreground.
- Prefer variant and size props on shadcn/ui components (e.g.
<Button variant="outline" size="sm">) over ad-hoc Tailwind color overrides.
- Use the
cn() utility (from @/lib/utils) to merge Tailwind classes safely: cn("base-class", conditionalClass).
⚠️ CRITICAL — Do NOT add a global * CSS reset:
Do NOT add * { margin: 0; padding: 0; box-sizing: border-box; } in globals.css. Tailwind v4's Preflight already applies proper resets. A manual * reset has equal specificity to Tailwind's utility classes and will override them when it appears later in the cascade, breaking padding (p-4, p-5, px-6, etc.) and margin utilities silently.
/* ❌ BROKEN — overrides Tailwind utilities like p-5, m-4 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* ✅ CORRECT — Tailwind v4 Preflight handles resets automatically */
@import "tailwindcss";
/* No manual * reset needed */
General Tailwind usage
- Typography: Use Tailwind's typography scale (
text-sm, text-base, text-lg, font-semibold, etc.) and keep heading/body consistent throughout the feature.
- Radius: Use consistent rounding from the theme (
rounded-md for cards and inputs; rounded-full for pills/avatars).
- Colors: Choose ONE approach based on whether shadcn/ui theming is set up:
- With shadcn/ui: Use its semantic tokens (
bg-background, text-foreground, bg-muted, text-muted-foreground, border) — these are pre-registered and work out of the box.
- Without shadcn/ui: Use Tailwind's built-in palette (
bg-white, text-slate-900, bg-slate-100, text-slate-500, border-slate-200).
- Never mix: Don't use
bg-background in a feature without shadcn/ui theming — it won't resolve. Reserve inline style for dynamic/computed colors only.
Hierarchy
Establish clear text hierarchy using the same approach as Colors above:
| Level |
With shadcn/ui |
Without shadcn/ui |
| Primary (main content) |
text-foreground |
text-slate-900 dark:text-slate-100 |
| Secondary (labels, metadata) |
text-muted-foreground |
text-slate-500 dark:text-slate-400 |
| Tertiary (timestamps, hints) |
Smaller size + text-muted-foreground |
Smaller size + text-slate-400 |
Reserve accent/brand color for CTAs and key UI, not body text.
UX principles
- Fast input: Optimize tab order, use presets or shortcuts where it fits, combine related steps (e.g. create + add in one flow).
- Feedback: Update UI right after mutations (invalidate queries, toasts for destructive or important actions). Use
disabled + a spinner on Button during submits.
- Low cognitive load: Few, clear actions per screen; constrained choices (e.g. fixed categories with icons); avoid wizards when a single form is enough.
- Empty and loading: Always handle empty data (centered message + short guidance) and loading (
Skeleton sized to final content, or a Loader2 spinner from Lucide).
Layout and spacing
- Spacing scale: Use Tailwind spacing consistently (e.g.
gap-2/p-2 for inline, gap-4/p-4 for cards/sections, gap-6/py-6 for page rhythm).
- Responsive: Use Tailwind breakpoints (
sm:, md:, lg:). Single column on mobile; sidebars/panels as toggles or Sheets (<Sheet>) on small screens. Use flex-wrap and min-w-0 to avoid overflow.
- Content width: Constrain main content (e.g.
max-w-2xl mx-auto) for readability on wide viewports.
Component patterns
- shadcn/ui first: Use shadcn/ui primitives for actions, forms, and feedback:
Button, Input, Card, Dialog, DropdownMenu, Select, Textarea, Badge, Skeleton, etc. Do not replace them with raw HTML for interactive elements.
- Forms: Use
react-hook-form with zod for validation. Wrap inputs in <FormField>, <FormItem>, <FormLabel>, <FormControl>, <FormMessage> from @/components/ui/form.
- Toast / notifications: Use
sonner (toast.success(...), toast.error(...)) or shadcn/ui's useToast hook. Prefer sonner for simplicity.
- Composition: Pass data and callbacks into components (e.g.
onSubmit, onClose), not big config objects.
- Icons: Use Lucide React (
lucide-react) consistently throughout the feature. Apply text-muted-foreground or contextual color classes for icon meaning (e.g. status, category).
- Loading: Add
disabled to the button and show a <Loader2 className="animate-spin" /> icon inside it while a request is in flight; use <Skeleton> for content placeholders.
State and feedback
- Cursor: Always add
cursor-pointer to interactive elements (buttons, links, clickable cards) that are not using shadcn/ui Button (which includes it automatically). For custom <button> elements or <div onClick> handlers, always include cursor-pointer in the Tailwind class list. For disabled states use cursor-not-allowed (and remove cursor-pointer).
- Hover/active: Rely on shadcn/ui component variants and Tailwind
hover: utilities; avoid overriding styles on interactive elements without a clear reason.
- Errors: Show inline validation messages via
<FormMessage> and a toast or inline alert for API errors. See handling-authentication-errors for 401/token expiry.
- Success: Toast or brief inline confirmation for saves and destructive actions.
Accessibility
- Use semantic structure: headings,
<label> for inputs, landmarks where relevant.
- shadcn/ui components are built on Radix UI primitives — keyboard navigation and ARIA attributes are handled automatically; do not override or remove them.
- Use
<FormLabel> for all form inputs so labels are always associated.
- Keep contrast in mind when picking palettes (CSS variable tokens help ensure consistency).
Dark mode
With shadcn/ui: Dark mode is toggled by adding/removing the dark class on <html>. Use CSS variable-based tokens (bg-background, text-foreground, etc.) which are registered through shadcn/ui's theme system.
Without shadcn/ui: Use Tailwind's dark: variant with the built-in palette: bg-white dark:bg-slate-900, text-slate-900 dark:text-slate-100. Do NOT create raw :root CSS variables and reference them via bg-[var(--name)] — this does not work in Tailwind v4 (see Tailwind CSS v4 section above).
References
- shadcn/ui: https://ui.shadcn.com — components, theming, and CLI usage.
- Radix UI: Underlying primitive library providing accessible behavior for shadcn/ui components.
- Lucide React: https://lucide.dev — icon set to use consistently.
- AGENTS.md: Use shadcn/ui for feature UIs; auth and SDK usage are described there and in fusebase-dashboards, handling-authentication-errors.
1---2name: app-ui-design3description: Guidance for visual design, UI and UX in Fusebase-generated app features. Use when building or refining feature UIs: pages, components, layouts, forms, feedback states, theming, or accessibility. Ensures consistent, clear, and distinctive interfaces using shadcn/ui.4---56# App UI Design78This skill guides UI/UX in **generated app features** (Fusebase Apps). Use **shadcn/ui** for all UI. Apply a clear design direction and avoid generic AI aesthetics.910---1112## Design philosophy1314**Clarity over decoration.** Every visual choice supports hierarchy and scannability. Prefer one bold aesthetic direction (minimal, warm, editorial, etc.) and execute it consistently.1516- **Purpose first**: What does the screen do? Who uses it?17- **Tone**: Choose one direction (e.g. minimal, warm/SaaS, editorial, utilitarian) and stick to it.18- **Differentiation**: Avoid default "AI" look: no Inter/Roboto-only, no purple gradients on white, no same-as-everyone layouts. Vary fonts, palette, and density per context.1920---2122## Visual identity2324### Tailwind CSS v42526Features use **Tailwind CSS v4** (via `@tailwindcss/postcss`). Key differences from v3:2728- **Import**: Use `@import "tailwindcss"` in `globals.css` (not `@tailwind base/components/utilities`).29- **No `tailwind.config.js`**: Configuration is CSS-first. Use `@theme` in CSS to define custom tokens.30- **Content detection is automatic**: Tailwind v4 scans project files automatically. If needed, use `@source "../path/**/*.tsx"` to add extra directories.3132**⚠️ CRITICAL — CSS variables in arbitrary values:**3334Do **NOT** define raw `:root` CSS variables and reference them via arbitrary value syntax. This is the most common Tailwind v4 pitfall:3536```css37/* ❌ BROKEN — variables are NOT available to Tailwind's arbitrary value resolver */38:root {39 --card: #ffffff;40 --foreground: #0f172a;41}42```43```tsx44// ❌ BROKEN — these classes compile but resolve to empty/broken values45className="bg-[var(--card)] text-[var(--foreground)]"46```4748**Instead, use one of these approaches:**49501. **Use Tailwind's built-in color palette** (preferred for most features):51 ```tsx52 // ✅ Works — uses Tailwind's first-class utility classes53 className="bg-white text-slate-900 border-slate-200"54 className="text-indigo-500 bg-indigo-50"55 ```56572. **Register custom tokens via `@theme`** (only if you need custom colors):58 ```css59 @import "tailwindcss";60 @theme {61 --color-brand: #6366f1;62 --color-surface: #ffffff;63 }64 ```65 ```tsx66 // ✅ Works — registered via @theme67 className="bg-surface text-brand"68 ```69703. **Use `@apply` for reusable component classes** (buttons, inputs):71 ```css72 .btn-primary {73 @apply inline-flex items-center gap-2 px-5 py-2.5 text-sm font-semibold text-white rounded-xl;74 background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);75 }76 ```77784. **Use inline `style` only for truly dynamic values** (e.g. colors from API data):79 ```tsx80 // ✅ OK — color is runtime data, can't be a utility class81 style={{ color: product.categoryColor, backgroundColor: `${product.categoryColor}18` }}82 ```8384### shadcn/ui (when used)8586- shadcn/ui components live in `components/ui/` (copied into the project, not installed as a package). Import from `@/components/ui/...`.87- When shadcn/ui is set up, its CSS variables (`--background`, `--foreground`, etc.) are registered through its own theme system and work with utility classes like `bg-background`, `text-foreground`.88- Prefer **variant** and **size** props on shadcn/ui components (e.g. `<Button variant="outline" size="sm">`) over ad-hoc Tailwind color overrides.89- Use the `cn()` utility (from `@/lib/utils`) to merge Tailwind classes safely: `cn("base-class", conditionalClass)`.9091**⚠️ CRITICAL — Do NOT add a global `*` CSS reset:**9293Do **NOT** add `* { margin: 0; padding: 0; box-sizing: border-box; }` in `globals.css`. Tailwind v4's Preflight already applies proper resets. A manual `*` reset has equal specificity to Tailwind's utility classes and will override them when it appears later in the cascade, breaking padding (`p-4`, `p-5`, `px-6`, etc.) and margin utilities silently.9495```css96/* ❌ BROKEN — overrides Tailwind utilities like p-5, m-4 */97* {98 margin: 0;99 padding: 0;100 box-sizing: border-box;101}102```103104```css105/* ✅ CORRECT — Tailwind v4 Preflight handles resets automatically */106@import "tailwindcss";107/* No manual * reset needed */108```109110### General Tailwind usage111112- **Typography**: Use Tailwind's typography scale (`text-sm`, `text-base`, `text-lg`, `font-semibold`, etc.) and keep heading/body consistent throughout the feature.113- **Radius**: Use consistent rounding from the theme (`rounded-md` for cards and inputs; `rounded-full` for pills/avatars).114- **Colors**: Choose ONE approach based on whether shadcn/ui theming is set up:115 - **With shadcn/ui**: Use its semantic tokens (`bg-background`, `text-foreground`, `bg-muted`, `text-muted-foreground`, `border`) — these are pre-registered and work out of the box.116 - **Without shadcn/ui**: Use Tailwind's built-in palette (`bg-white`, `text-slate-900`, `bg-slate-100`, `text-slate-500`, `border-slate-200`).117 - **Never mix**: Don't use `bg-background` in a feature without shadcn/ui theming — it won't resolve. Reserve inline `style` for dynamic/computed colors only.118119### Hierarchy120121Establish clear text hierarchy using the same approach as Colors above:122123| Level | With shadcn/ui | Without shadcn/ui |124|-------|----------------|-------------------|125| **Primary** (main content) | `text-foreground` | `text-slate-900 dark:text-slate-100` |126| **Secondary** (labels, metadata) | `text-muted-foreground` | `text-slate-500 dark:text-slate-400` |127| **Tertiary** (timestamps, hints) | Smaller size + `text-muted-foreground` | Smaller size + `text-slate-400` |128129Reserve accent/brand color for CTAs and key UI, not body text.130131---132133## UX principles134135- **Fast input**: Optimize tab order, use presets or shortcuts where it fits, combine related steps (e.g. create + add in one flow).136- **Feedback**: Update UI right after mutations (invalidate queries, toasts for destructive or important actions). Use `disabled` + a spinner on `Button` during submits.137- **Low cognitive load**: Few, clear actions per screen; constrained choices (e.g. fixed categories with icons); avoid wizards when a single form is enough.138- **Empty and loading**: Always handle empty data (centered message + short guidance) and loading (`Skeleton` sized to final content, or a `Loader2` spinner from Lucide).139140---141142## Layout and spacing143144- **Spacing scale**: Use Tailwind spacing consistently (e.g. `gap-2`/`p-2` for inline, `gap-4`/`p-4` for cards/sections, `gap-6`/`py-6` for page rhythm).145- **Responsive**: Use Tailwind breakpoints (`sm:`, `md:`, `lg:`). Single column on mobile; sidebars/panels as toggles or Sheets (`<Sheet>`) on small screens. Use `flex-wrap` and `min-w-0` to avoid overflow.146- **Content width**: Constrain main content (e.g. `max-w-2xl mx-auto`) for readability on wide viewports.147148---149150## Component patterns151152- **shadcn/ui first**: Use shadcn/ui primitives for actions, forms, and feedback: `Button`, `Input`, `Card`, `Dialog`, `DropdownMenu`, `Select`, `Textarea`, `Badge`, `Skeleton`, etc. Do not replace them with raw HTML for interactive elements.153- **Forms**: Use `react-hook-form` with `zod` for validation. Wrap inputs in `<FormField>`, `<FormItem>`, `<FormLabel>`, `<FormControl>`, `<FormMessage>` from `@/components/ui/form`.154- **Toast / notifications**: Use `sonner` (`toast.success(...)`, `toast.error(...)`) or shadcn/ui's `useToast` hook. Prefer `sonner` for simplicity.155- **Composition**: Pass data and callbacks into components (e.g. `onSubmit`, `onClose`), not big config objects.156- **Icons**: Use **Lucide React** (`lucide-react`) consistently throughout the feature. Apply `text-muted-foreground` or contextual color classes for icon meaning (e.g. status, category).157- **Loading**: Add `disabled` to the button and show a `<Loader2 className="animate-spin" />` icon inside it while a request is in flight; use `<Skeleton>` for content placeholders.158159---160161## State and feedback162163- **Cursor**: Always add `cursor-pointer` to interactive elements (buttons, links, clickable cards) that are not using shadcn/ui `Button` (which includes it automatically). For custom `<button>` elements or `<div onClick>` handlers, always include `cursor-pointer` in the Tailwind class list. For disabled states use `cursor-not-allowed` (and remove `cursor-pointer`).164- **Hover/active**: Rely on shadcn/ui component variants and Tailwind `hover:` utilities; avoid overriding styles on interactive elements without a clear reason.165- **Errors**: Show inline validation messages via `<FormMessage>` and a toast or inline alert for API errors. See **handling-authentication-errors** for 401/token expiry.166- **Success**: Toast or brief inline confirmation for saves and destructive actions.167168---169170## Accessibility171172- Use semantic structure: headings, `<label>` for inputs, landmarks where relevant.173- shadcn/ui components are built on Radix UI primitives — keyboard navigation and ARIA attributes are handled automatically; do not override or remove them.174- Use `<FormLabel>` for all form inputs so labels are always associated.175- Keep contrast in mind when picking palettes (CSS variable tokens help ensure consistency).176177---178179## Dark mode180181**With shadcn/ui**: Dark mode is toggled by adding/removing the `dark` class on `<html>`. Use CSS variable-based tokens (`bg-background`, `text-foreground`, etc.) which are registered through shadcn/ui's theme system.182183**Without shadcn/ui**: Use Tailwind's `dark:` variant with the built-in palette: `bg-white dark:bg-slate-900`, `text-slate-900 dark:text-slate-100`. Do NOT create raw `:root` CSS variables and reference them via `bg-[var(--name)]` — this does not work in Tailwind v4 (see Tailwind CSS v4 section above).184185---186187## References188189- **shadcn/ui**: https://ui.shadcn.com — components, theming, and CLI usage.190- **Radix UI**: Underlying primitive library providing accessible behavior for shadcn/ui components.191- **Lucide React**: https://lucide.dev — icon set to use consistently.192- **AGENTS.md**: Use shadcn/ui for feature UIs; auth and SDK usage are described there and in **fusebase-dashboards**, **handling-authentication-errors**.