Nitrofy LogoNitrofy
Como funcionaBeneficiosIntegracoesPlanosDuvidas
Comece agora
Nitrofy Logo
Comecar
Introduction
QuickstartEnvironment SetupRunning LocallyFirst Deploy
Organizations & TenancyAuthentication & SessionsRoles & PermissionsControllers & ProceduresJobs & QueuesPlugin ManagerContent LayerBuilt-in MCP ServerBillingNotificationsFile StorageEmailWebhooksAPI KeysSEOData FetchingDesign System
Development

Design System

Comprehensive design system with Tailwind CSS, shadcn UI components, theming, and accessibility patterns.

By the end of this guide, you'll have mastered the SaaS Boilerplate design system, enabling you to build consistent, accessible, and beautiful user interfaces with Tailwind CSS, shadcn UI components, and proper theming.

Overview

The SaaS Boilerplate includes a comprehensive design system built on modern web standards, featuring Tailwind CSS for styling, shadcn UI for component primitives, Radix UI for accessibility, and a robust theming system. Key features include:

  • Tailwind CSS: Utility-first CSS framework with custom design tokens
  • shadcn UI: High-quality React components built on Radix UI primitives
  • Design Tokens: Consistent colors, typography, spacing, and shadows
  • Dark Mode: Complete dark/light theme support with system preference detection
  • Accessibility: WCAG-compliant components with proper ARIA attributes
  • Responsive Design: Mobile-first approach with breakpoint system
  • Animation System: Smooth transitions and micro-interactions
  • Type Safety: Full TypeScript support with proper component APIs
  • Performance: Optimized bundle size and runtime performance

The system ensures design consistency across your application while providing flexibility for customization and extension.

Architecture

Tailwind CSS Foundation

The design system is built on Tailwind CSS with custom configuration:

// tailwind.config.ts - Custom design tokens
const config: Config = {
  theme: {
    fontFamily: {
      sans: ["geist"],
      mono: ["geist-mono"],
    },
    extend: {
      colors: {
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: "hsl(var(--primary))",
        // ... extensive color system
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
      // ... animations, shadows, spacing
    }
  }
}

shadcn UI Component Library

Components are built using shadcn UI patterns with Radix UI primitives:

// Component structure pattern
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, ...props }, ref) => {
    return (
      <button
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    )
  }
)
Button.displayName = "Button"

CSS Variables and Theming

The system uses CSS custom properties for theming:

/* src/app/globals.css */
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --primary: oklch(0.205 0 0);
  /* ... extensive color palette */
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  /* ... dark mode overrides */
}

Setting Up the Design System

Install Dependencies

The design system dependencies are pre-installed. Key packages include:

{
  "dependencies": {
    "tailwindcss": "^3.4.0",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.0.0",
    "tailwind-merge": "^2.0.0",
    "@radix-ui/react-slot": "^1.0.0",
    "next-themes": "^0.2.0"
  }
}

Configure Tailwind CSS

The Tailwind configuration is already set up with custom design tokens:

// tailwind.config.ts
import type { Config } from "tailwindcss";

const config: Config = {
  darkMode: ["class"],
  content: [
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
    "./node_modules/@tremor/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    // Custom configuration with design tokens
  },
  plugins: [
    require("tailwindcss-animate"),
    require("@headlessui/tailwindcss"),
    require("@tailwindcss/forms"),
    require("@tailwindcss/typography"),
  ],
};

Set Up Theme Provider

The theme provider enables dark mode switching:

// src/app/layout.tsx
import { ThemeProvider } from '@/components/ui/theme-provider'

export default function RootLayout({ children }) {
  return (
    <ThemeProvider
      attribute="class"
      defaultTheme="system"
      enableSystem
      disableTransitionOnChange
    >
      {children}
    </ThemeProvider>
  )
}

Import Global Styles

Ensure global styles are imported:

// src/app/layout.tsx
import './globals.css'

Design Tokens

Color System

The design system uses a comprehensive color palette with semantic naming:

Prop

Type

Typography Scale

Typography follows a consistent scale with Geist font family:

Prop

Type

Spacing Scale

Consistent spacing using a rem-based scale:

Prop

Type

Component Patterns

Button Variants

The Button component supports multiple variants and sizes:

// Available variants
type ButtonVariant = 
  | "default" 
  | "destructive" 
  | "outline" 
  | "secondary" 
  | "ghost" 
  | "link"

type ButtonSize = "default" | "sm" | "lg" | "icon"

// Usage
<Button variant="default" size="sm">Click me</Button>
<Button variant="outline" size="lg">Outline</Button>
<Button variant="ghost">Ghost</Button>

Form Components

Form components follow consistent patterns with proper validation:

// Form structure
<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)}>
    <FormField
      control={form.control}
      name="email"
      render={({ field }) => (
        <FormItem>
          <FormLabel>Email</FormLabel>
          <FormControl>
            <Input placeholder="Enter email" {...field} />
          </FormControl>
          <FormMessage />
        </FormItem>
      )}
    />
    <Button type="submit">Submit</Button>
  </form>
</Form>

Layout Components

Layout components provide consistent spacing and structure:

// Page layout pattern
<PageWrapper>
  <PageHeader>
    <PageMainBar>
      <Breadcrumb />
    </PageMainBar>
    <PageActionsBar>
      <Button>Create</Button>
    </PageActionsBar>
  </PageHeader>
  <PageBody>
    <div className="container max-w-(--breakpoint-md)">
      {/* Page content */}
    </div>
  </PageBody>
</PageWrapper>

Theming and Dark Mode

Theme Provider Setup

The theme provider manages light/dark mode switching:

// src/components/ui/theme-provider.tsx
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

// Usage in layout
<ThemeProvider
  attribute="class"
  defaultTheme="system"
  enableSystem
  disableTransitionOnChange
>
  {children}
</ThemeProvider>

Theme Toggle Component

Theme switching component with system preference detection:

// src/components/ui/theme-toggle.tsx
export function ThemeToggle() {
  const { theme, setTheme } = useTheme()
  
  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      <Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
    </Button>
  )
}

CSS Variable Updates

Dynamic theme color updates for mobile browsers:

// Theme color script in theme provider
const THEME_COLOR_SCRIPT = `
(function() {
  var html = document.documentElement;
  var meta = document.querySelector('meta[name="theme-color"]');
  if (!meta) {
    meta = document.createElement('meta');
    meta.setAttribute('name', 'theme-color');
    document.head.appendChild(meta);
  }
  function updateThemeColor() {
    var isDark = html.classList.contains('dark');
    meta.setAttribute('content', isDark ? DARK_THEME_COLOR : LIGHT_THEME_COLOR);
  }
  var observer = new MutationObserver(updateThemeColor);
  observer.observe(html, { attributes: true, attributeFilter: ['class'] });
  updateThemeColor();
})();
`

Practical Examples

Building a Dashboard Card

Complete dashboard card with proper styling and theming:

function DashboardCard({ 
  title, 
  value, 
  change, 
  icon: Icon 
}: DashboardCardProps) {
  return (
    <Card>
      <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
        <CardTitle className="text-sm font-medium">{title}</CardTitle>
        <Icon className="h-4 w-4 text-muted-foreground" />
      </CardHeader>
      <CardContent>
        <div className="text-2xl font-bold">{value}</div>
        <p className="text-xs text-muted-foreground">
          {change > 0 ? '+' : ''}{change}% from last month
        </p>
      </CardContent>
    </Card>
  )
}

// Usage
<DashboardCard
  title="Total Revenue"
  value="$45,231.89"
  change={20.1}
  icon={DollarSign}
/>

Form with Validation

Complete form implementation with validation and error handling:

function UserProfileForm() {
  const form = useFormWithZod({
    schema: userProfileSchema,
    defaultValues: {
      name: '',
      email: '',
      bio: '',
    },
  })

  return (
    <Form {...form}>
      <form onSubmit={form.onSubmit} className="space-y-6">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Full Name</FormLabel>
              <FormControl>
                <Input placeholder="John Doe" {...field} />
              </FormControl>
              <FormDescription>
                This is your public display name.
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />

        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input type="email" placeholder="john@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <FormField
          control={form.control}
          name="bio"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Bio</FormLabel>
              <FormControl>
                <Textarea
                  placeholder="Tell us about yourself..."
                  className="resize-none"
                  {...field}
                />
              </FormControl>
              <FormDescription>
                You can write up to 500 characters.
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />

        <Button type="submit" disabled={form.formState.isSubmitting}>
          {form.formState.isSubmitting ? 'Saving...' : 'Save Changes'}
        </Button>
      </form>
    </Form>
  )
}

Data Table with Actions

Advanced data table with sorting, filtering, and actions:

function UsersTable({ users }: { users: User[] }) {
  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <div>
          <h2 className="text-2xl font-bold tracking-tight">Users</h2>
          <p className="text-muted-foreground">
            Manage your team members and their permissions.
          </p>
        </div>
        <Button>
          <Plus className="mr-2 h-4 w-4" />
          Add User
        </Button>
      </div>

      <Card>
        <CardHeader>
          <CardTitle>Team Members</CardTitle>
          <CardDescription>
            A list of all users in your organization.
          </CardDescription>
        </CardHeader>
        <CardContent>
          <Table>
            <TableHeader>
              <TableRow>
                <TableHead>Name</TableHead>
                <TableHead>Email</TableHead>
                <TableHead>Role</TableHead>
                <TableHead>Status</TableHead>
                <TableHead className="text-right">Actions</TableHead>
              </TableRow>
            </TableHeader>
            <TableBody>
              {users.map((user) => (
                <TableRow key={user.id}>
                  <TableCell className="font-medium">{user.name}</TableCell>
                  <TableCell>{user.email}</TableCell>
                  <TableCell>
                    <Badge variant={user.role === 'admin' ? 'default' : 'secondary'}>
                      {user.role}
                    </Badge>
                  </TableCell>
                  <TableCell>
                    <Badge variant={user.status === 'active' ? 'default' : 'secondary'}>
                      {user.status}
                    </Badge>
                  </TableCell>
                  <TableCell className="text-right">
                    <DropdownMenu>
                      <DropdownMenuTrigger asChild>
                        <Button variant="ghost" className="h-8 w-8 p-0">
                          <MoreHorizontal className="h-4 w-4" />
                        </Button>
                      </DropdownMenuTrigger>
                      <DropdownMenuContent align="end">
                        <DropdownMenuItem>Edit</DropdownMenuItem>
                        <DropdownMenuItem className="text-destructive">
                          Delete
                        </DropdownMenuItem>
                      </DropdownMenuContent>
                    </DropdownMenu>
                  </TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Table>
        </CardContent>
      </Card>
    </div>
  )
}

Accessibility Features

ARIA Attributes

Components include proper ARIA attributes for screen readers:

// Button with accessibility
<Button 
  aria-label="Close dialog"
  aria-expanded={isOpen}
  onClick={handleClick}
>
  <X className="h-4 w-4" />
</Button>

// Form with proper labeling
<FormField
  control={form.control}
  name="email"
  render={({ field }) => (
    <FormItem>
      <FormLabel htmlFor="email">Email Address</FormLabel>
      <FormControl>
        <Input 
          id="email"
          type="email" 
          aria-describedby="email-error"
          {...field} 
        />
      </FormControl>
      <FormMessage id="email-error" />
    </FormItem>
  )}
/>

Focus Management

Proper focus indicators and keyboard navigation:

// Focus ring styles in globals.css
.focus-visible\:ring-2:focus-visible {
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}

// Component with focus management
<Dialog>
  <DialogTrigger asChild>
    <Button>Open Dialog</Button>
  </DialogTrigger>
  <DialogContent>
    {/* Dialog content with proper focus management */}
  </DialogContent>
</Dialog>

Color Contrast

Design tokens ensure proper contrast ratios:

// Color contrast validation
const colors = {
  background: 'oklch(1 0 0)',      // Light background
  foreground: 'oklch(0.145 0 0)',  // Dark text - high contrast
  
  // Dark mode
  darkBackground: 'oklch(0.145 0 0)', // Dark background  
  darkForeground: 'oklch(0.985 0 0)', // Light text - high contrast
}

Animation System

CSS Transitions

Smooth transitions for interactive elements:

// Button hover transitions
const buttonVariants = cva(
  'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
  // ... variants
)

// Custom animations in tailwind.config.ts
keyframes: {
  "accordion-down": {
    from: { height: "0" },
    to: { height: "var(--radix-accordion-content-height)" },
  },
  "accordion-up": {
    from: { height: "var(--radix-accordion-content-height)" },
    to: { height: "0" },
  },
  // ... more animations
}

Framer Motion Integration

Advanced animations using Framer Motion:

import { motion } from 'framer-motion'

function AnimatedCard({ children, delay = 0 }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ 
        duration: 0.3, 
        delay,
        ease: 'easeOut' 
      }}
    >
      <Card>{children}</Card>
    </motion.div>
  )
}

// Usage
<AnimatePresence>
  {items.map((item, index) => (
    <AnimatedCard key={item.id} delay={index * 0.1}>
      {item.content}
    </AnimatedCard>
  ))}
</AnimatePresence>

Troubleshooting

Best Practices

See Also

  • Forms and Validation - Form handling patterns
  • SEO - Design system impact on SEO
  • Performance and Security - Design system performance considerations
  • Routing and Navigation - Navigation patterns
  • Styling and UI - Advanced styling techniques

API Reference

Component Variants

Prop

Type

Utility Functions

Prop

Type

CSS Custom Properties

Prop

Type

Animation Classes

Prop

Type

Data Fetching

Comprehensive data fetching strategies with Igniter client, server actions, ISR, and real-time updates.

Meet Lia

Meet Lia, your specialized AI Code Agent for the SaaS Boilerplate and Igniter.js ecosystem, designed to accelerate development and maintain high-quality code.

On this page

OverviewArchitectureTailwind CSS Foundationshadcn UI Component LibraryCSS Variables and ThemingSetting Up the Design SystemInstall DependenciesConfigure Tailwind CSSSet Up Theme ProviderImport Global StylesDesign TokensColor SystemTypography ScaleSpacing ScaleComponent PatternsButton VariantsForm ComponentsLayout ComponentsTheming and Dark ModeTheme Provider SetupTheme Toggle ComponentCSS Variable UpdatesPractical ExamplesBuilding a Dashboard CardForm with ValidationData Table with ActionsAccessibility FeaturesARIA AttributesFocus ManagementColor ContrastAnimation SystemCSS TransitionsFramer Motion IntegrationTroubleshootingBest PracticesSee AlsoAPI ReferenceComponent VariantsUtility FunctionsCSS Custom PropertiesAnimation Classes
Nitrofy LogoNitrofy

Automatize o envio e a cobrança dos seus contratos

© 2026 Nitrofy, All rights reserved