Next.js Supabase V3 (#463)
Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
This commit is contained in:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
278
docs/customization/fonts.mdoc
Normal file
278
docs/customization/fonts.mdoc
Normal file
@@ -0,0 +1,278 @@
|
||||
---
|
||||
status: "published"
|
||||
label: "Updating Fonts"
|
||||
title: "Customize Application Fonts | Next.js Supabase SaaS Kit"
|
||||
order: 3
|
||||
description: "Configure custom fonts using Google Fonts, local fonts, or system fonts in your Makerkit application with Next.js font optimization."
|
||||
---
|
||||
|
||||
Customize your application's typography by editing `apps/web/lib/fonts.ts`. This file defines the font families used throughout your app, with Next.js automatically handling font optimization, subsetting, and self-hosting for privacy and performance.
|
||||
|
||||
By default, Makerkit uses Apple's system font on Apple devices (San Francisco) and falls back to Inter on other platforms.
|
||||
|
||||
## Quick Font Change
|
||||
|
||||
Replace the default Inter font with any Google Font:
|
||||
|
||||
```tsx title="apps/web/lib/fonts.ts"
|
||||
import { Poppins as SansFont } from 'next/font/google';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
const sans = SansFont({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-sans-fallback',
|
||||
fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'],
|
||||
preload: true,
|
||||
weight: ['300', '400', '500', '600', '700'],
|
||||
});
|
||||
|
||||
const heading = sans;
|
||||
|
||||
export { sans, heading };
|
||||
|
||||
export function getFontsClassName(theme?: string) {
|
||||
const dark = theme === 'dark';
|
||||
const light = !dark;
|
||||
|
||||
const font = [sans.variable, heading.variable].reduce<string[]>(
|
||||
(acc, curr) => {
|
||||
if (acc.includes(curr)) return acc;
|
||||
return [...acc, curr];
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return cn(...font, { dark, light });
|
||||
}
|
||||
```
|
||||
|
||||
## Using Different Fonts for Headings and Body
|
||||
|
||||
Create visual hierarchy by using different fonts for headings and body text:
|
||||
|
||||
```tsx title="apps/web/lib/fonts.ts"
|
||||
import { Inter as SansFont, Playfair_Display as HeadingFont } from 'next/font/google';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
const sans = SansFont({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-sans-fallback',
|
||||
fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'],
|
||||
preload: true,
|
||||
weight: ['300', '400', '500', '600', '700'],
|
||||
});
|
||||
|
||||
const heading = HeadingFont({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-heading',
|
||||
fallback: ['Georgia', 'Times New Roman', 'serif'],
|
||||
preload: true,
|
||||
weight: ['400', '500', '600', '700'],
|
||||
});
|
||||
|
||||
export { sans, heading };
|
||||
|
||||
export function getFontsClassName(theme?: string) {
|
||||
const dark = theme === 'dark';
|
||||
const light = !dark;
|
||||
|
||||
const font = [sans.variable, heading.variable].reduce<string[]>(
|
||||
(acc, curr) => {
|
||||
if (acc.includes(curr)) return acc;
|
||||
return [...acc, curr];
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return cn(...font, { dark, light });
|
||||
}
|
||||
```
|
||||
|
||||
Then update `apps/web/styles/shadcn-ui.css` to use the heading font in the `@theme inline` block:
|
||||
|
||||
```css title="apps/web/styles/shadcn-ui.css"
|
||||
@theme inline {
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, var(--font-sans-fallback);
|
||||
--font-heading: var(--font-heading), Georgia, serif;
|
||||
}
|
||||
```
|
||||
|
||||
## Using Local Fonts
|
||||
|
||||
For fonts not available on Google Fonts, or for complete control over font files:
|
||||
|
||||
```tsx title="apps/web/lib/fonts.ts"
|
||||
import localFont from 'next/font/local';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
const sans = localFont({
|
||||
src: [
|
||||
{
|
||||
path: '../fonts/CustomFont-Regular.woff2',
|
||||
weight: '400',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: '../fonts/CustomFont-Medium.woff2',
|
||||
weight: '500',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: '../fonts/CustomFont-Bold.woff2',
|
||||
weight: '700',
|
||||
style: 'normal',
|
||||
},
|
||||
],
|
||||
variable: '--font-sans-fallback',
|
||||
fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'],
|
||||
preload: true,
|
||||
});
|
||||
|
||||
const heading = sans;
|
||||
|
||||
export { sans, heading };
|
||||
```
|
||||
|
||||
Place font files in `apps/web/fonts/` directory. Supported formats: `.woff2` (recommended), `.woff`, `.ttf`, `.otf`.
|
||||
|
||||
## Removing Apple System Font Default
|
||||
|
||||
By default, Makerkit prioritizes Apple's system font on macOS and iOS for a native feel. To use your chosen font consistently across all platforms:
|
||||
|
||||
Edit the `@theme inline` block in `apps/web/styles/shadcn-ui.css`:
|
||||
|
||||
```css title="apps/web/styles/shadcn-ui.css"
|
||||
@theme inline {
|
||||
/* Remove -apple-system and BlinkMacSystemFont to use your font everywhere */
|
||||
--font-sans: var(--font-sans-fallback);
|
||||
--font-heading: var(--font-sans);
|
||||
}
|
||||
```
|
||||
|
||||
This ensures your Google Font or local font displays on Apple devices instead of San Francisco.
|
||||
|
||||
## Popular Font Combinations
|
||||
|
||||
### Modern SaaS (Clean and Professional)
|
||||
```tsx
|
||||
import { Inter as SansFont } from 'next/font/google';
|
||||
// Headings and body: Inter
|
||||
```
|
||||
|
||||
### Editorial (Content-Heavy Apps)
|
||||
```tsx
|
||||
import { Source_Sans_3 as SansFont, Source_Serif_4 as HeadingFont } from 'next/font/google';
|
||||
// Body: Source Sans 3
|
||||
// Headings: Source Serif 4
|
||||
```
|
||||
|
||||
### Startup (Friendly and Approachable)
|
||||
```tsx
|
||||
import { DM_Sans as SansFont } from 'next/font/google';
|
||||
// Headings and body: DM Sans
|
||||
```
|
||||
|
||||
### Technical (Developer Tools)
|
||||
```tsx
|
||||
import { IBM_Plex_Sans as SansFont, IBM_Plex_Mono as MonoFont } from 'next/font/google';
|
||||
// Body: IBM Plex Sans
|
||||
// Code: IBM Plex Mono
|
||||
```
|
||||
|
||||
### Premium (Luxury/Finance)
|
||||
```tsx
|
||||
import { Outfit as SansFont } from 'next/font/google';
|
||||
// Headings and body: Outfit
|
||||
```
|
||||
|
||||
## Font Variable Reference
|
||||
|
||||
The font system uses CSS variables defined in two places:
|
||||
|
||||
| Variable | Defined In | Purpose |
|
||||
|----------|------------|---------|
|
||||
| `--font-sans-fallback` | `fonts.ts` | Next.js optimized font |
|
||||
| `--font-heading` | `fonts.ts` | Heading font (if different) |
|
||||
| `--font-sans` | `shadcn-ui.css` | Final font stack with system fallbacks |
|
||||
|
||||
Tailwind uses these through `theme.css`:
|
||||
|
||||
```css title="apps/web/styles/shadcn-ui.css"
|
||||
@theme inline {
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, var(--font-sans-fallback);
|
||||
--font-heading: var(--font-sans);
|
||||
}
|
||||
```
|
||||
|
||||
## Optimizing Font Loading
|
||||
|
||||
### Preload Critical Fonts
|
||||
```tsx
|
||||
const sans = SansFont({
|
||||
// ...
|
||||
preload: true, // Preload for faster initial render
|
||||
display: 'swap', // Show fallback immediately, swap when loaded
|
||||
});
|
||||
```
|
||||
|
||||
### Subset for Faster Loading
|
||||
```tsx
|
||||
const sans = SansFont({
|
||||
// ...
|
||||
subsets: ['latin'], // Only load Latin characters
|
||||
// Or load multiple subsets if needed:
|
||||
// subsets: ['latin', 'latin-ext', 'cyrillic'],
|
||||
});
|
||||
```
|
||||
|
||||
### Specify Only Needed Weights
|
||||
```tsx
|
||||
const sans = SansFont({
|
||||
// ...
|
||||
weight: ['400', '500', '700'], // Only weights you actually use
|
||||
// Avoid: weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900']
|
||||
});
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
**Loading too many font weights**: Each weight adds to bundle size. Only include weights you actually use (typically 400, 500, 600, 700).
|
||||
|
||||
**Forgetting to update CSS variables**: After changing fonts in `fonts.ts`, you may need to update `shadcn-ui.css` if you want to remove the Apple system font priority or configure the heading font.
|
||||
|
||||
**Using display: 'block'**: This causes invisible text until fonts load (FOIT). Use `display: 'swap'` for better perceived performance.
|
||||
|
||||
**Not testing on Windows**: Apple system fonts don't exist on Windows. Always test your fallback fonts on non-Apple devices.
|
||||
|
||||
## Verification
|
||||
|
||||
After updating fonts:
|
||||
|
||||
1. Check the Network tab in DevTools for font files loading
|
||||
2. Verify fonts render on both Mac and Windows
|
||||
3. Test with slow network throttling to see fallback behavior
|
||||
4. Run Lighthouse to check for font-related performance issues
|
||||
|
||||
```bash
|
||||
# Quick check for font loading
|
||||
pnpm dev
|
||||
# Open DevTools > Network > Filter: Font
|
||||
# Verify your custom font files are loading
|
||||
```
|
||||
|
||||
{% faq
|
||||
title="Frequently Asked Questions"
|
||||
items=[
|
||||
{"question": "Why does my custom font not appear on Mac?", "answer": "By default, Makerkit prioritizes Apple system fonts. Edit shadcn-ui.css and remove -apple-system and BlinkMacSystemFont from the --font-sans variable to use your custom font on Apple devices."},
|
||||
{"question": "How do I add a monospace font for code blocks?", "answer": "Import a monospace font in fonts.ts, export it, and add a --font-mono CSS variable. Then configure it in theme.css. Consider IBM Plex Mono, JetBrains Mono, or Fira Code."},
|
||||
{"question": "Can I use variable fonts?", "answer": "Yes. Next.js supports variable fonts. Specify weight as a range: weight: '100 900'. This loads a single file that supports all weights, often smaller than multiple static font files."},
|
||||
{"question": "How do I improve font loading performance?", "answer": "Limit font weights to those you use, enable preload: true, use display: 'swap', and only load needed subsets. Variable fonts can also reduce total download size."}
|
||||
]
|
||||
/%}
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Back to [Customization Overview](/docs/next-supabase-turbo/customization)
|
||||
- Configure your [theme colors](/docs/next-supabase-turbo/customization/theme) to complement your typography
|
||||
- Set up your [layout style](/docs/next-supabase-turbo/customization/layout-style) for navigation
|
||||
- Update your [application logo](/docs/next-supabase-turbo/customization/logo)
|
||||
285
docs/customization/layout-style.mdoc
Normal file
285
docs/customization/layout-style.mdoc
Normal file
@@ -0,0 +1,285 @@
|
||||
---
|
||||
status: "published"
|
||||
label: "Layout Style"
|
||||
title: "Configure Navigation Layout | Next.js Supabase SaaS Kit"
|
||||
order: 4
|
||||
description: "Choose between sidebar and header navigation layouts, configure collapsed states, and customize the navigation experience for your SaaS application."
|
||||
---
|
||||
|
||||
Makerkit offers two navigation layouts: **sidebar** (default) and **header**. You can configure each workspace independently, with separate settings for personal accounts and team accounts. All layout options are controlled through environment variables.
|
||||
|
||||
## Quick Configuration
|
||||
|
||||
Set your preferred layout in `.env.local`:
|
||||
|
||||
```bash title=".env.local"
|
||||
# Personal account workspace layout
|
||||
NEXT_PUBLIC_USER_NAVIGATION_STYLE=sidebar
|
||||
|
||||
# Team account workspace layout
|
||||
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=sidebar
|
||||
```
|
||||
|
||||
Available values: `sidebar`, `header`, or `custom`.
|
||||
|
||||
## Layout Options Compared
|
||||
|
||||
### Sidebar Layout (Default)
|
||||
|
||||
The sidebar layout places navigation on the left side of the screen, providing a persistent, vertically-oriented menu.
|
||||
|
||||
{% img src="/assets/images/docs/turbo-sidebar-layout.webp" width="2522" height="1910" /%}
|
||||
|
||||
**Best for:**
|
||||
- Apps with many navigation items (5+)
|
||||
- Complex feature sets needing categorized menus
|
||||
- Desktop-first applications
|
||||
- Enterprise or admin-heavy dashboards
|
||||
|
||||
**Configuration:**
|
||||
```bash title=".env.local"
|
||||
NEXT_PUBLIC_USER_NAVIGATION_STYLE=sidebar
|
||||
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=sidebar
|
||||
```
|
||||
|
||||
### Header Layout
|
||||
|
||||
The header layout places navigation horizontally at the top of the screen, with a more compact, traditional web app feel.
|
||||
|
||||
{% img src="/assets/images/docs/turbo-header-layout.webp" width="3282" height="1918" /%}
|
||||
|
||||
**Best for:**
|
||||
- Apps with fewer navigation items (3-5)
|
||||
- Consumer-facing products
|
||||
- Mobile-first designs
|
||||
- Simple, focused applications
|
||||
|
||||
**Configuration:**
|
||||
```bash title=".env.local"
|
||||
NEXT_PUBLIC_USER_NAVIGATION_STYLE=header
|
||||
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=header
|
||||
```
|
||||
|
||||
## Sidebar Behavior Options
|
||||
|
||||
### Default Collapsed State
|
||||
|
||||
Control whether the sidebar starts expanded or collapsed:
|
||||
|
||||
```bash title=".env.local"
|
||||
# Personal account sidebar (home workspace)
|
||||
NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED=false
|
||||
|
||||
# Team account sidebar
|
||||
NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED=false
|
||||
```
|
||||
|
||||
Set to `true` to start with a collapsed icon-only sidebar. Users can still expand it manually.
|
||||
|
||||
### Collapsible Style
|
||||
|
||||
Choose how the sidebar collapses and expands:
|
||||
|
||||
```bash title=".env.local"
|
||||
# Options: offcanvas, icon, none
|
||||
NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE=offcanvas
|
||||
```
|
||||
|
||||
| Style | Behavior |
|
||||
|-------|----------|
|
||||
| `offcanvas` | Sidebar slides in/out as an overlay (mobile-friendly) |
|
||||
| `icon` | Sidebar collapses to icons only, expanding on hover |
|
||||
| `none` | Sidebar cannot be collapsed |
|
||||
|
||||
### Sidebar Trigger Visibility
|
||||
|
||||
Show or hide the sidebar toggle button:
|
||||
|
||||
```bash title=".env.local"
|
||||
NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER=true
|
||||
```
|
||||
|
||||
Set to `false` if you want the sidebar to remain in its configured state without user control.
|
||||
|
||||
## Complete Configuration Example
|
||||
|
||||
Here's a full example for a team-focused SaaS with different layouts per workspace:
|
||||
|
||||
```bash title=".env.local"
|
||||
# Personal account: simple header navigation
|
||||
NEXT_PUBLIC_USER_NAVIGATION_STYLE=header
|
||||
|
||||
# Team workspace: full sidebar with collapsed default
|
||||
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=sidebar
|
||||
NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED=true
|
||||
NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE=icon
|
||||
NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER=true
|
||||
|
||||
# Theme settings
|
||||
NEXT_PUBLIC_DEFAULT_THEME_MODE=system
|
||||
NEXT_PUBLIC_ENABLE_THEME_TOGGLE=true
|
||||
```
|
||||
|
||||
## Customizing Navigation Items
|
||||
|
||||
Navigation items are defined in configuration files, not environment variables:
|
||||
|
||||
| Workspace | Configuration File |
|
||||
|-----------|-------------------|
|
||||
| Personal Account | `apps/web/config/personal-account-navigation.config.tsx` |
|
||||
| Team Account | `apps/web/config/team-account-navigation.config.tsx` |
|
||||
|
||||
### Personal Account Navigation
|
||||
|
||||
```tsx title="apps/web/config/personal-account-navigation.config.tsx"
|
||||
import { CreditCard, Home, User, Settings } from 'lucide-react';
|
||||
import { NavigationConfigSchema } from '@kit/ui/navigation-schema';
|
||||
import pathsConfig from '~/config/paths.config';
|
||||
|
||||
const iconClasses = 'w-4';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
label: 'common.routes.application',
|
||||
children: [
|
||||
{
|
||||
label: 'common.routes.home',
|
||||
path: pathsConfig.app.home,
|
||||
Icon: <Home className={iconClasses} />,
|
||||
highlightMatch: `${pathsConfig.app.home}$`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'common.routes.settings',
|
||||
children: [
|
||||
{
|
||||
label: 'common.routes.profile',
|
||||
path: pathsConfig.app.personalAccountSettings,
|
||||
Icon: <User className={iconClasses} />,
|
||||
},
|
||||
{
|
||||
label: 'common.routes.billing',
|
||||
path: pathsConfig.app.personalAccountBilling,
|
||||
Icon: <CreditCard className={iconClasses} />,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const personalAccountNavigationConfig = NavigationConfigSchema.parse({
|
||||
routes,
|
||||
style: process.env.NEXT_PUBLIC_USER_NAVIGATION_STYLE,
|
||||
sidebarCollapsed: process.env.NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED,
|
||||
sidebarCollapsedStyle: process.env.NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE,
|
||||
});
|
||||
```
|
||||
|
||||
### Adding a New Navigation Item
|
||||
|
||||
To add a custom page to the navigation:
|
||||
|
||||
```tsx title="apps/web/config/personal-account-navigation.config.tsx"
|
||||
import { BarChart3 } from 'lucide-react';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
label: 'common.routes.application',
|
||||
children: [
|
||||
{
|
||||
label: 'common.routes.home',
|
||||
path: pathsConfig.app.home,
|
||||
Icon: <Home className={iconClasses} />,
|
||||
highlightMatch: `${pathsConfig.app.home}$`,
|
||||
},
|
||||
// Add your custom navigation item
|
||||
{
|
||||
label: 'common.routes.analytics',
|
||||
path: '/home/analytics',
|
||||
Icon: <BarChart3 className={iconClasses} />,
|
||||
},
|
||||
],
|
||||
},
|
||||
// ... rest of routes
|
||||
];
|
||||
```
|
||||
|
||||
Remember to add the translation key to your locale files:
|
||||
|
||||
```json title="apps/web/i18n/messages/en/common.json"
|
||||
{
|
||||
"routes": {
|
||||
"analytics": "Analytics"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Navigation Schema Reference
|
||||
|
||||
The `NavigationConfigSchema` supports these properties:
|
||||
|
||||
```typescript
|
||||
interface NavigationConfig {
|
||||
routes: {
|
||||
label: string; // Translation key for section header
|
||||
collapsible?: boolean; // Allow section to collapse (default: false)
|
||||
children: {
|
||||
label: string; // Translation key for item
|
||||
path: string; // Route path
|
||||
Icon?: ReactNode; // Lucide icon component
|
||||
highlightMatch?: string; // Regex pattern for active route highlighting
|
||||
}[];
|
||||
}[];
|
||||
style?: 'sidebar' | 'header' | 'custom';
|
||||
sidebarCollapsed?: boolean | string;
|
||||
sidebarCollapsedStyle?: 'offcanvas' | 'icon' | 'none';
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables Reference
|
||||
|
||||
| Variable | Default | Options | Description |
|
||||
|----------|---------|---------|-------------|
|
||||
| `NEXT_PUBLIC_USER_NAVIGATION_STYLE` | `sidebar` | `sidebar`, `header`, `custom` | Personal account layout |
|
||||
| `NEXT_PUBLIC_TEAM_NAVIGATION_STYLE` | `sidebar` | `sidebar`, `header`, `custom` | Team account layout |
|
||||
| `NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED` | `false` | `true`, `false` | Personal sidebar default state |
|
||||
| `NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED` | `false` | `true`, `false` | Team sidebar default state |
|
||||
| `NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE` | `icon` | `offcanvas`, `icon`, `none` | Collapse behavior |
|
||||
| `NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER` | `true` | `true`, `false` | Show collapse toggle |
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
**Mixing layout styles inconsistently**: If personal accounts use header layout but teams use sidebar, the experience can feel disjointed. Consider the transition between workspaces.
|
||||
|
||||
**Too many items in header layout**: Header navigation works best with 3-5 top-level items. More than that causes horizontal overflow or cramped spacing. Use sidebar layout for complex navigation.
|
||||
|
||||
**Forgetting mobile behavior**: Sidebar layout automatically converts to a slide-out drawer on mobile. Test both layouts on narrow viewports.
|
||||
|
||||
**Not updating translations**: Navigation labels use translation keys. Adding items without corresponding translations shows raw keys like `common.routes.analytics`.
|
||||
|
||||
## Verification
|
||||
|
||||
After changing layout configuration:
|
||||
|
||||
1. Clear your browser's local storage (layout preferences are cached)
|
||||
2. Restart the dev server for environment variable changes
|
||||
3. Test both personal account and team account workspaces
|
||||
4. Verify mobile responsiveness at 375px viewport width
|
||||
5. Check that sidebar collapse/expand works correctly
|
||||
|
||||
{% faq
|
||||
title="Frequently Asked Questions"
|
||||
items=[
|
||||
{"question": "Can I use different layouts for personal and team accounts?", "answer": "Yes. Set NEXT_PUBLIC_USER_NAVIGATION_STYLE and NEXT_PUBLIC_TEAM_NAVIGATION_STYLE to different values. This is useful when team workspaces need more navigation complexity than personal accounts."},
|
||||
{"question": "How do I create a completely custom layout?", "answer": "Set the navigation style to 'custom' and implement your own layout component. You'll need to modify the layout files in apps/web/app/home/ to use your custom navigation component."},
|
||||
{"question": "Why isn't my sidebar staying collapsed?", "answer": "User preferences are stored in local storage and override environment defaults. Clear local storage or use the browser's incognito mode to test default behavior."},
|
||||
{"question": "How do I add icons to navigation items?", "answer": "Import icons from lucide-react and pass them as the Icon prop. Use className='w-4' to maintain consistent sizing with other navigation icons."}
|
||||
]
|
||||
/%}
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Back to [Customization Overview](/docs/next-supabase-turbo/customization)
|
||||
- Configure your [theme colors](/docs/next-supabase-turbo/customization/theme) to match your navigation style
|
||||
- Set up [custom fonts](/docs/next-supabase-turbo/customization/fonts) for navigation typography
|
||||
- Update your [application logo](/docs/next-supabase-turbo/customization/logo) for the sidebar/header
|
||||
194
docs/customization/logo.mdoc
Normal file
194
docs/customization/logo.mdoc
Normal file
@@ -0,0 +1,194 @@
|
||||
---
|
||||
status: "published"
|
||||
label: "Updating the Logo"
|
||||
title: "Customize Your Application Logo | Next.js Supabase SaaS Kit"
|
||||
order: 1
|
||||
description: "Replace the default Makerkit logo with your own brand logo using SVG, image files, or custom React components."
|
||||
---
|
||||
|
||||
Replace the default Makerkit logo by editing the `AppLogo` component at `apps/web/components/app-logo.tsx`. This single component controls the logo across your entire application: authentication pages, site header, footer, sidebar, and email templates.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Open `apps/web/components/app-logo.tsx` and replace the existing SVG with your logo:
|
||||
|
||||
```tsx title="apps/web/components/app-logo.tsx"
|
||||
import Link from 'next/link';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
function LogoImage({ className }: { className?: string }) {
|
||||
return (
|
||||
<img
|
||||
src="/images/logo.svg"
|
||||
alt="Your Company Name"
|
||||
className={cn('w-[80px] lg:w-[95px]', className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppLogo({
|
||||
href,
|
||||
label,
|
||||
className,
|
||||
}: {
|
||||
href?: string | null;
|
||||
className?: string;
|
||||
label?: string;
|
||||
}) {
|
||||
if (href === null) {
|
||||
return <LogoImage className={className} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link aria-label={label ?? 'Home Page'} href={href ?? '/'}>
|
||||
<LogoImage className={className} />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Place your logo file in `apps/web/public/images/` and update the `src` path accordingly.
|
||||
|
||||
## Logo Implementation Options
|
||||
|
||||
### Option 1: SVG Component (Recommended)
|
||||
|
||||
Inline SVGs provide the best performance and allow dynamic styling with Tailwind classes:
|
||||
|
||||
```tsx title="apps/web/components/app-logo.tsx"
|
||||
function LogoImage({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={cn('w-[95px] h-auto', className)}
|
||||
viewBox="0 0 100 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="fill-primary dark:fill-white"
|
||||
d="M10 5h80v22H10z"
|
||||
/>
|
||||
{/* Your SVG paths */}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Supports `fill-primary` for automatic theme color adaptation
|
||||
- Responds to dark mode with `dark:fill-white`
|
||||
- Scales without quality loss
|
||||
- No additional HTTP requests
|
||||
|
||||
### Option 2: Next.js Image Component
|
||||
|
||||
For PNG, JPG, or WebP logos, use `next/image` for automatic optimization:
|
||||
|
||||
```tsx title="apps/web/components/app-logo.tsx"
|
||||
import Image from 'next/image';
|
||||
|
||||
function LogoImage({ className }: { className?: string }) {
|
||||
return (
|
||||
<Image
|
||||
src="/images/logo.png"
|
||||
alt="Your Company Name"
|
||||
width={95}
|
||||
height={32}
|
||||
className={cn('w-[80px] lg:w-[95px] h-auto', className)}
|
||||
priority
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Option 3: Dark Mode Variants
|
||||
|
||||
When your logo needs different versions for light and dark modes:
|
||||
|
||||
```tsx title="apps/web/components/app-logo.tsx"
|
||||
import Image from 'next/image';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
function LogoImage({ className }: { className?: string }) {
|
||||
return (
|
||||
<>
|
||||
<Image
|
||||
src="/images/logo-dark.svg"
|
||||
alt="Your Company Name"
|
||||
width={95}
|
||||
height={32}
|
||||
className={cn('hidden dark:block w-[80px] lg:w-[95px]', className)}
|
||||
priority
|
||||
/>
|
||||
<Image
|
||||
src="/images/logo-light.svg"
|
||||
alt="Your Company Name"
|
||||
width={95}
|
||||
height={32}
|
||||
className={cn('block dark:hidden w-[80px] lg:w-[95px]', className)}
|
||||
priority
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Where the Logo Appears
|
||||
|
||||
The `AppLogo` component renders in these locations:
|
||||
|
||||
| Location | File Path | Notes |
|
||||
|----------|-----------|-------|
|
||||
| Site Header | `packages/ui/src/makerkit/marketing/header.tsx` | Marketing pages |
|
||||
| Site Footer | `packages/ui/src/makerkit/marketing/footer.tsx` | All pages |
|
||||
| Auth Pages | `apps/web/app/[locale]/auth/layout.tsx` | Sign in, sign up |
|
||||
| App Sidebar | `packages/ui/src/makerkit/sidebar-navigation.tsx` | Dashboard (when team accounts disabled) |
|
||||
| Email Templates | `packages/email-templates/src/` | Transactional emails |
|
||||
|
||||
## Favicon and Social Images
|
||||
|
||||
Update these additional brand assets in `apps/web/app/`:
|
||||
|
||||
```
|
||||
apps/web/app/
|
||||
├── favicon.ico # Browser tab icon (32x32)
|
||||
├── icon.png # PWA icon (512x512)
|
||||
├── apple-icon.png # iOS home screen (180x180)
|
||||
└── opengraph-image.png # Social sharing (1200x630)
|
||||
```
|
||||
|
||||
Generate these from your logo using tools like [RealFaviconGenerator](https://realfavicongenerator.net/) or [Favicon.io](https://favicon.io/).
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
**Using low-resolution images**: Logos appear blurry on high-DPI displays. Always use SVG when possible, or provide 2x/3x image assets.
|
||||
|
||||
**Forgetting alt text**: Screen readers need descriptive alt text. Use your company name, not "logo".
|
||||
|
||||
**Hard-coded dimensions**: Use responsive classes like `w-[80px] lg:w-[95px]` instead of fixed pixel widths to ensure the logo scales appropriately on mobile.
|
||||
|
||||
**Missing priority attribute**: Add `priority` to Next.js Image components for above-the-fold logos to prevent layout shift.
|
||||
|
||||
## Verification
|
||||
|
||||
After updating your logo:
|
||||
|
||||
1. Check the marketing header at `http://localhost:3000`
|
||||
2. Verify the auth pages at `http://localhost:3000/auth/sign-in`
|
||||
3. Test dark mode toggle to confirm logo visibility
|
||||
4. Inspect mobile viewport (375px width) for proper sizing
|
||||
|
||||
{% faq
|
||||
title="Frequently Asked Questions"
|
||||
items=[
|
||||
{"question": "How do I make my SVG logo change color with the theme?", "answer": "Use Tailwind's fill classes on your SVG paths: fill-primary for the default theme color, or dark:fill-white to change in dark mode. Remove any hardcoded fill attributes from the SVG."},
|
||||
{"question": "What size should my logo be?", "answer": "Design for 95px width on desktop and 80px on mobile. SVGs scale automatically. For raster images, export at 2x resolution (190x64 pixels minimum) to support high-DPI displays."},
|
||||
{"question": "Can I use different logos in different parts of the app?", "answer": "Yes. You can modify the AppLogo component to accept a variant prop or create separate components. However, maintaining brand consistency is recommended."},
|
||||
{"question": "How do I update the logo in email templates?", "answer": "Email templates use the same AppLogo component where possible, but some email clients require inline images. Check packages/email-templates/src/components/ for email-specific logo handling."}
|
||||
]
|
||||
/%}
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Back to [Customization Overview](/docs/next-supabase-turbo/customization)
|
||||
- Configure your [brand colors and theme](/docs/next-supabase-turbo/customization/theme)
|
||||
- Customize your [application fonts](/docs/next-supabase-turbo/customization/fonts)
|
||||
236
docs/customization/tailwind-css.mdoc
Normal file
236
docs/customization/tailwind-css.mdoc
Normal file
@@ -0,0 +1,236 @@
|
||||
---
|
||||
status: "published"
|
||||
label: "Tailwind CSS"
|
||||
title: "Tailwind CSS Configuration | Next.js Supabase SaaS Kit"
|
||||
order: -1
|
||||
description: "Configure Tailwind CSS 4, extend the design system, and customize styles across your Makerkit monorepo application."
|
||||
---
|
||||
|
||||
Makerkit uses Tailwind CSS 4 with Shadcn UI for styling. All style configuration lives in `apps/web/styles/`, with the main entry point at `globals.css`. This guide covers how to customize Tailwind, add new packages to the content paths, and extend the design system.
|
||||
|
||||
## Style File Structure
|
||||
|
||||
The styling system uses these files in `apps/web/styles/`:
|
||||
|
||||
```
|
||||
apps/web/styles/
|
||||
├── globals.css # Main entry point, imports everything
|
||||
├── theme.css # Theme color variables (light/dark mode, :root/.dark)
|
||||
├── shadcn-ui.css # Maps CSS variables to Tailwind's @theme inline
|
||||
├── makerkit.css # Makerkit-specific component styles
|
||||
└── markdoc.css # Content/documentation styles
|
||||
```
|
||||
|
||||
## Tailwind CSS 4 Configuration
|
||||
|
||||
Tailwind CSS 4 uses CSS-based configuration instead of JavaScript. The `@theme inline` directive in `shadcn-ui.css` maps your CSS variables to Tailwind design tokens:
|
||||
|
||||
```css title="apps/web/styles/shadcn-ui.css"
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
|
||||
/* Border radius */
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
|
||||
/* Font families */
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, var(--font-sans-fallback);
|
||||
--font-heading: var(--font-sans);
|
||||
}
|
||||
```
|
||||
|
||||
The actual color values are defined in `theme.css` using oklch format (see [Theme Colors](/docs/next-supabase-turbo/customization/theme)).
|
||||
|
||||
These tokens become available as Tailwind utilities: `bg-primary`, `text-foreground`, `rounded-lg`, etc.
|
||||
|
||||
## Adding Content Paths for New Packages
|
||||
|
||||
When you create a new package in the monorepo, Tailwind needs to know where to scan for class names. Add a `@source` directive in `apps/web/styles/globals.css`:
|
||||
|
||||
```css title="apps/web/styles/globals.css"
|
||||
@import 'tailwindcss';
|
||||
@import 'tw-animate-css';
|
||||
|
||||
/* local styles */
|
||||
@import './theme.css';
|
||||
@import './shadcn-ui.css';
|
||||
@import './markdoc.css';
|
||||
@import './makerkit.css';
|
||||
|
||||
/* content sources - update the below if you add a new path */
|
||||
@source '../../../packages/*/src/**/*.{ts,tsx}';
|
||||
@source '../../../packages/features/*/src/**/*.{ts,tsx}';
|
||||
@source '../../../packages/billing/*/src/**/*.{ts,tsx}';
|
||||
@source '../../../packages/plugins/*/src/**/*.{ts,tsx}';
|
||||
@source '../../../packages/cms/*/src/**/*.{ts,tsx}';
|
||||
@source '../{app,components,config,lib}/**/*.{ts,tsx}';
|
||||
|
||||
/* Add your new package here */
|
||||
@source '../../../packages/your-package/src/**/*.{ts,tsx}';
|
||||
```
|
||||
|
||||
The `@source` directive is the Tailwind CSS 4 replacement for the `content` array in the old `tailwind.config.ts`.
|
||||
|
||||
## Custom Utility Classes
|
||||
|
||||
Add custom utilities using the `@utility` directive in `makerkit.css` or a new CSS file:
|
||||
|
||||
```css title="apps/web/styles/makerkit.css"
|
||||
@utility container {
|
||||
@apply mx-auto px-4 lg:px-8 xl:max-w-[80rem];
|
||||
}
|
||||
```
|
||||
|
||||
Or add utilities in a `@layer`:
|
||||
|
||||
```css
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.scrollbar-hidden {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.scrollbar-hidden::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Extending the Theme
|
||||
|
||||
Add custom design tokens in `shadcn-ui.css` inside the `@theme inline` block:
|
||||
|
||||
```css title="apps/web/styles/shadcn-ui.css"
|
||||
@theme inline {
|
||||
/* ... existing tokens ... */
|
||||
|
||||
/* Custom colors */
|
||||
--color-brand: var(--brand);
|
||||
--color-brand-light: var(--brand-light);
|
||||
}
|
||||
```
|
||||
|
||||
Then define the values in `theme.css`:
|
||||
|
||||
```css title="apps/web/styles/theme.css"
|
||||
:root {
|
||||
--brand: oklch(65% 0.2 250);
|
||||
--brand-light: oklch(85% 0.1 250);
|
||||
}
|
||||
```
|
||||
|
||||
Use these in your components:
|
||||
|
||||
```tsx
|
||||
<div className="bg-brand text-brand-light">
|
||||
Content
|
||||
</div>
|
||||
```
|
||||
|
||||
## Component-Level Styles
|
||||
|
||||
For complex component styles, use `@layer components` in `makerkit.css`:
|
||||
|
||||
```css title="apps/web/styles/makerkit.css"
|
||||
@layer components {
|
||||
.card-hover {
|
||||
@apply transition-all duration-200;
|
||||
@apply hover:shadow-lg hover:-translate-y-0.5;
|
||||
}
|
||||
|
||||
.btn-gradient {
|
||||
@apply bg-gradient-to-r from-primary to-accent;
|
||||
@apply text-primary-foreground;
|
||||
@apply hover:opacity-90 transition-opacity;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Shadcn UI Component Customization
|
||||
|
||||
Override Shadcn component styles by targeting their classes:
|
||||
|
||||
```css title="apps/web/styles/shadcn-ui.css"
|
||||
@layer components {
|
||||
/* Custom button variants */
|
||||
.btn-primary-gradient {
|
||||
@apply bg-gradient-to-r from-blue-600 to-indigo-600;
|
||||
@apply hover:from-blue-700 hover:to-indigo-700;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dark Mode Utilities
|
||||
|
||||
Create dark-mode-aware utilities using the `dark:` variant:
|
||||
|
||||
```css
|
||||
@layer utilities {
|
||||
.glass {
|
||||
@apply bg-white/80 backdrop-blur-sm;
|
||||
@apply dark:bg-neutral-900/80;
|
||||
}
|
||||
|
||||
.surface-elevated {
|
||||
@apply bg-white shadow-sm;
|
||||
@apply dark:bg-neutral-800 dark:shadow-none;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The dark variant is configured in `theme.css` as:
|
||||
|
||||
```css
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
**Missing content paths**: New packages won't have their Tailwind classes compiled if you forget to add a `@source` directive. Styles will appear missing in production even if they work in development.
|
||||
|
||||
**Using `@apply` excessively**: Reserve `@apply` for reusable component patterns. For one-off styles, use utility classes directly in JSX. Excessive `@apply` increases CSS bundle size.
|
||||
|
||||
**Forgetting `@layer` directives**: Custom styles without `@layer` can have specificity issues. Always wrap custom styles in `@layer base`, `@layer components`, or `@layer utilities`.
|
||||
|
||||
**Hardcoding colors**: Use theme variables (`bg-primary`, `text-foreground`) instead of hardcoded colors (`bg-blue-500`). This ensures consistency and makes theme changes easier.
|
||||
|
||||
## Verification
|
||||
|
||||
After modifying Tailwind configuration:
|
||||
|
||||
1. Restart the dev server (Tailwind config changes require a restart)
|
||||
2. Run `pnpm build` to verify all classes compile correctly
|
||||
3. Check production build for missing styles: `pnpm build && pnpm start`
|
||||
4. Verify dark mode works for any new utilities
|
||||
|
||||
```bash
|
||||
# Quick verification commands
|
||||
pnpm dev # Development server
|
||||
pnpm build # Production build (catches missing content paths)
|
||||
pnpm typecheck # Type checking
|
||||
```
|
||||
|
||||
{% faq
|
||||
title="Frequently Asked Questions"
|
||||
items=[
|
||||
{"question": "Why are my Tailwind classes not working in production?", "answer": "The most common cause is missing @source directives in globals.css. Add your package path as a @source directive and rebuild. Classes must exist in scanned files to be included in the production CSS bundle."},
|
||||
{"question": "How do I add a completely custom color?", "answer": "Define the CSS variable in theme.css (:root block), e.g. --brand: oklch(65% 0.2 250). Then map it in shadcn-ui.css inside @theme inline: --color-brand: var(--brand). Use it as bg-brand or text-brand in your components."},
|
||||
{"question": "Should I use @apply or inline utilities?", "answer": "Prefer inline utilities for most cases. Use @apply only for frequently repeated patterns that need to stay in sync. Inline utilities are more explicit and easier to maintain."},
|
||||
{"question": "How do I override Shadcn component styles?", "answer": "Add overrides in shadcn-ui.css within @layer components. Target the specific component classes or create variant classes. You can also pass className props to override individual instances."}
|
||||
]
|
||||
/%}
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Back to [Customization Overview](/docs/next-supabase-turbo/customization)
|
||||
- Configure your [theme colors](/docs/next-supabase-turbo/customization/theme) for brand consistency
|
||||
- Set up [custom fonts](/docs/next-supabase-turbo/customization/fonts) for typography
|
||||
- Choose your [layout style](/docs/next-supabase-turbo/customization/layout-style) for navigation
|
||||
252
docs/customization/theme.mdoc
Normal file
252
docs/customization/theme.mdoc
Normal file
@@ -0,0 +1,252 @@
|
||||
---
|
||||
status: "published"
|
||||
label: "Updating the Theme"
|
||||
title: "Customize Your Shadcn UI Theme Colors | Next.js Supabase SaaS Kit"
|
||||
order: 0
|
||||
description: "Configure brand colors, dark mode, and Shadcn UI theme variables in your Makerkit application using Tailwind CSS 4."
|
||||
---
|
||||
|
||||
Customize your application's color scheme by editing `apps/web/styles/theme.css`. This file defines all theme variables (`:root` and `.dark`) that Shadcn UI components use, giving you complete control over your brand colors in both light and dark modes.
|
||||
|
||||
## Quick Theme Change
|
||||
|
||||
The fastest way to update your theme is to use the [Shadcn UI Themes page](https://ui.shadcn.com/themes):
|
||||
|
||||
1. Choose a color scheme on the Shadcn theme builder
|
||||
2. Copy the generated CSS variables
|
||||
3. Paste them into `apps/web/styles/theme.css`
|
||||
4. Wrap color values with `hsl()` or `oklch()` functions (Tailwind CSS 4 requirement)
|
||||
|
||||
## Theme File Structure
|
||||
|
||||
Makerkit's theming uses three CSS files in `apps/web/styles/`:
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `theme.css` | Your theme colors - `:root` and `.dark` variables (edit this file) |
|
||||
| `shadcn-ui.css` | Maps CSS variables to Tailwind's `@theme inline` system |
|
||||
| `globals.css` | Imports all styles and base Tailwind directives |
|
||||
|
||||
## Core Theme Variables
|
||||
|
||||
Edit `apps/web/styles/theme.css` to customize these color groups. Colors use oklch format:
|
||||
|
||||
```css title="apps/web/styles/theme.css"
|
||||
:root {
|
||||
/* Background and text */
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
|
||||
/* Primary brand color (buttons, links, focus rings) */
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
|
||||
/* Secondary actions and elements */
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
|
||||
/* Muted backgrounds and text */
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
|
||||
/* Hover states and accents */
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
|
||||
/* Destructive actions (delete, error) */
|
||||
--destructive: oklch(0.58 0.22 27);
|
||||
|
||||
/* Cards and popovers */
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
|
||||
/* Borders and inputs */
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
|
||||
/* Border radius */
|
||||
--radius: 0.625rem;
|
||||
|
||||
/* Sidebar-specific colors */
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
|
||||
/* Chart colors */
|
||||
--chart-1: oklch(0.809 0.105 251.813);
|
||||
--chart-2: oklch(0.623 0.214 259.815);
|
||||
--chart-3: oklch(0.546 0.245 262.881);
|
||||
--chart-4: oklch(0.488 0.243 264.376);
|
||||
--chart-5: oklch(0.424 0.199 265.638);
|
||||
}
|
||||
```
|
||||
|
||||
## Dark Mode Configuration
|
||||
|
||||
Define dark mode colors in the `.dark` class within the same file:
|
||||
|
||||
```css title="apps/web/styles/theme.css"
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
|
||||
--primary: oklch(0.87 0 0);
|
||||
--primary-foreground: oklch(0.16 0 0);
|
||||
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
|
||||
--accent: oklch(0.371 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
|
||||
--card: oklch(0.16 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.16 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
|
||||
--sidebar: oklch(0.16 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
```
|
||||
|
||||
## Converting Shadcn Theme Colors to Tailwind CSS 4
|
||||
|
||||
Shadcn's theme builder outputs HSL values without the function wrapper. Tailwind CSS 4 requires explicit color functions.
|
||||
|
||||
**Shadcn output:**
|
||||
```css
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
```
|
||||
|
||||
**Tailwind CSS 4 format:**
|
||||
```css
|
||||
--primary: hsl(222.2 47.4% 11.2%);
|
||||
```
|
||||
|
||||
You can also use `oklch()` for better color perception:
|
||||
```css
|
||||
--primary: oklch(21.03% 0.0318 264.65);
|
||||
```
|
||||
|
||||
Use any AI tool or color converter to transform the values. The key is ensuring every color value is wrapped in a color function.
|
||||
|
||||
## Using Tailwind Color Palette
|
||||
|
||||
Reference Tailwind's built-in colors using CSS variables:
|
||||
|
||||
```css
|
||||
--primary: var(--color-blue-600);
|
||||
--destructive: var(--color-red-500);
|
||||
--accent: var(--color-indigo-100);
|
||||
```
|
||||
|
||||
Available color scales: `slate`, `gray`, `zinc`, `neutral`, `stone`, `red`, `orange`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`.
|
||||
|
||||
Each scale includes shades from `50` (lightest) to `950` (darkest).
|
||||
|
||||
## Theme Mode Configuration
|
||||
|
||||
Control how theme switching works with these environment variables:
|
||||
|
||||
```bash title=".env.local"
|
||||
# Default theme: light, dark, or system
|
||||
NEXT_PUBLIC_DEFAULT_THEME_MODE=system
|
||||
|
||||
# Show/hide the theme toggle in the UI
|
||||
NEXT_PUBLIC_ENABLE_THEME_TOGGLE=true
|
||||
```
|
||||
|
||||
## Custom Brand Color Example
|
||||
|
||||
Here's a complete example using a custom indigo brand color:
|
||||
|
||||
```css title="apps/web/styles/theme.css"
|
||||
:root {
|
||||
--primary: oklch(0.457 0.24 277.023); /* indigo-600 */
|
||||
--primary-foreground: oklch(1 0 0); /* white */
|
||||
|
||||
--secondary: oklch(0.943 0.029 282.832); /* indigo-100 */
|
||||
--secondary-foreground: oklch(0.272 0.174 282.572); /* indigo-900 */
|
||||
|
||||
--accent: oklch(0.969 0.014 282.832); /* indigo-50 */
|
||||
--accent-foreground: oklch(0.272 0.174 282.572);
|
||||
|
||||
--ring: oklch(0.539 0.233 277.117); /* indigo-500 */
|
||||
}
|
||||
|
||||
.dark {
|
||||
--primary: oklch(0.673 0.208 277.568); /* indigo-400 */
|
||||
--primary-foreground: oklch(0.208 0.153 283.264); /* indigo-950 */
|
||||
|
||||
--secondary: oklch(0.272 0.174 282.572); /* indigo-900 */
|
||||
--secondary-foreground: oklch(0.943 0.029 282.832);
|
||||
|
||||
--accent: oklch(0.351 0.209 281.288); /* indigo-800 */
|
||||
--accent-foreground: oklch(0.943 0.029 282.832);
|
||||
|
||||
--ring: oklch(0.673 0.208 277.568); /* indigo-400 */
|
||||
}
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
**Forgetting color function wrappers**: Tailwind CSS 4 requires `hsl()`, `oklch()`, or `rgb()` around color values. Raw space-separated values like `222 47% 11%` won't work.
|
||||
|
||||
**Low contrast ratios**: Ensure sufficient contrast between foreground and background colors. Use tools like [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) to verify WCAG compliance.
|
||||
|
||||
**Inconsistent dark mode**: Always define dark mode variants for every color you customize. Missing dark mode variables cause jarring visual inconsistencies.
|
||||
|
||||
**Not testing all components**: Theme changes affect every Shadcn component. After updating colors, click through your app to verify buttons, inputs, cards, and dialogs all look correct.
|
||||
|
||||
## Verification
|
||||
|
||||
After updating your theme:
|
||||
|
||||
1. Start the dev server: `pnpm dev`
|
||||
2. Toggle between light and dark modes
|
||||
3. Check these component types:
|
||||
- Primary buttons (`--primary`)
|
||||
- Form inputs (`--input`, `--border`, `--ring`)
|
||||
- Cards and dialogs (`--card`, `--popover`)
|
||||
- Destructive actions (`--destructive`)
|
||||
- Sidebar navigation (`--sidebar-*` variables)
|
||||
|
||||
{% faq
|
||||
title="Frequently Asked Questions"
|
||||
items=[
|
||||
{"question": "How do I use a custom color not in Tailwind's palette?", "answer": "Define your color using hsl() or oklch() functions directly. For example: --primary: hsl(250 60% 45%). You can use any valid CSS color value wrapped in a color function."},
|
||||
{"question": "Why do my colors look different than the Shadcn theme preview?", "answer": "Tailwind CSS 4 requires explicit color functions (hsl, oklch, rgb). Convert space-separated HSL values to hsl() function calls. Also ensure you're using the same color space."},
|
||||
{"question": "Can I have different themes for different pages?", "answer": "The theme applies globally. For page-specific styling, use CSS classes or component-level overrides rather than modifying theme variables. You could also implement a theme context for programmatic switching."},
|
||||
{"question": "How do I disable dark mode entirely?", "answer": "Set NEXT_PUBLIC_DEFAULT_THEME_MODE=light and NEXT_PUBLIC_ENABLE_THEME_TOGGLE=false in your environment variables. This forces light mode and hides the toggle."}
|
||||
]
|
||||
/%}
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Back to [Customization Overview](/docs/next-supabase-turbo/customization)
|
||||
- Set up your [Tailwind CSS configuration](/docs/next-supabase-turbo/customization/tailwind-css) for additional customizations
|
||||
- Configure [custom fonts](/docs/next-supabase-turbo/customization/fonts) for your brand typography
|
||||
- Update your [application logo](/docs/next-supabase-turbo/customization/logo) to match your theme
|
||||
Reference in New Issue
Block a user