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:
Giancarlo Buomprisco
2026-03-24 13:40:38 +08:00
committed by GitHub
parent 4912e402a3
commit 7ebff31475
840 changed files with 71395 additions and 20095 deletions

View 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)

View 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

View 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)

View 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

View 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