SavvySolve Docs

Styling System

Tailwind CSS v4, shadcn/ui, and SavvySolve brand theming configuration.

Styling System

SavvySolve uses Tailwind CSS v4 for utility-first styling combined with shadcn/ui for pre-built accessible components. The styling system implements the SavvySolve brand identity with a custom color palette featuring orange (#FF8A0D) as the primary accent and purple (#1B0F40) for dark mode backgrounds.

Brand Identity

The SavvySolve brand uses three core colors:

ColorHexUsage
Orange#FF8A0DPrimary actions, accents, active states
Purple#1B0F40Dark mode background, secondary elements
White#FFFFFFLight mode background, text on dark

The brand font is Poppins for headings and body text, with JetBrains Mono for code blocks.

Logo Assets

Logo files are stored in public/logos/ with standardized naming for different use cases:

FileBackgroundUsage
icon-light.svgLightIcon on light backgrounds (orange background)
icon-dark.svgDarkIcon on dark backgrounds (purple background)
logo-horizontal-light.svgLightFull logo on light backgrounds ("Savvy" orange, "Solve" purple)
logo-horizontal-dark.svgDarkFull logo on dark backgrounds (all orange)
logo-oauth.svgAnySquare logo for OAuth/social login buttons

Usage Guidelines

Light mode / Light backgrounds:

<img src="/logos/icon-light.svg" alt="SavvySolve" />
<img src="/logos/logo-horizontal-light.svg" alt="SavvySolve" />

Dark mode / Dark backgrounds:

<img src="/logos/icon-dark.svg" alt="SavvySolve" />
<img src="/logos/logo-horizontal-dark.svg" alt="SavvySolve" />

Theme-aware logo component:

import { useTheme } from "next-themes";

function Logo() {
  const { resolvedTheme } = useTheme();
  const logoSrc = resolvedTheme === "dark" 
    ? "/logos/logo-horizontal-dark.svg"
    : "/logos/logo-horizontal-light.svg";
  
  return <img src={logoSrc} alt="SavvySolve" className="h-8" />;
}

PWA Icons

PWA icons are generated from icon-light.svg (the orange icon) and stored in public/icons/. To regenerate after logo updates:

bun run scripts/generate-pwa-icons.ts

This generates all required sizes: 72, 96, 128, 144, 152, 192, 384, 512px plus maskable variants and the Apple touch icon.

Why This Stack?

The PRD emphasizes several UI requirements that influenced these choices:

  • Mobile-first design (60%+ expected mobile traffic) - Tailwind's responsive utilities make this natural
  • Large touch targets for senior users - shadcn/ui components follow accessibility guidelines
  • WCAG AA compliance - Both tools prioritize accessibility by default
  • Rapid iteration - The solver dashboard and session interfaces need fast development cycles

Tailwind CSS v4

Tailwind v4 introduces CSS-first configuration, eliminating the need for a separate tailwind.config.js file. All configuration happens directly in CSS using the @theme directive.

CSS Configuration

The styling system is configured in the global stylesheet:

app/globals.css
@import "tailwindcss";
@import "fumadocs-ui/css/preset.css";
@import "tw-animate-css";

Note that we import only preset.css from Fumadocs (not neutral.css) because we define our own complete color theme.

Custom Theme

The SavvySolve theme defines all colors in the @theme block for light mode:

app/globals.css
@theme {
    /* Fonts */
    --font-sans: var(--font-poppins);
    --font-mono: var(--font-jetbrains-mono);
    
    /* Light mode colors */
    --color-fd-background: hsl(0, 0%, 100%);
    --color-fd-foreground: hsl(256, 45%, 14%);
    --color-fd-primary: hsl(33, 100%, 52%);
    --color-fd-primary-foreground: hsl(0, 0%, 100%);
    --color-fd-accent: hsl(33, 80%, 95%);
    --color-fd-accent-foreground: hsl(33, 90%, 30%);
    --color-fd-ring: hsl(33, 100%, 52%);
    /* ... additional color definitions */
}

Dark mode overrides use the .dark selector outside the @theme block:

app/globals.css
.dark {
    --color-fd-background: hsl(256, 63%, 8%);
    --color-fd-foreground: hsl(0, 0%, 92%);
    --color-fd-primary: hsl(33, 100%, 55%);
    --color-fd-primary-foreground: hsl(0, 0%, 100%);
    --color-fd-card: hsl(256, 55%, 11%);
    /* ... additional dark mode overrides */
}

This approach ensures our brand colors take precedence over any preset defaults.

Font Configuration

Fonts are loaded in the root layout using Next.js font optimization:

app/layout.tsx
import { Poppins, JetBrains_Mono } from "next/font/google";

const poppins = Poppins({
  variable: "--font-poppins",
  subsets: ["latin"],
  weight: ["300", "400", "500", "600", "700"],
});

const jetbrainsMono = JetBrains_Mono({
  variable: "--font-jetbrains-mono",
  subsets: ["latin"],
});

The CSS variables --font-poppins and --font-jetbrains-mono are then referenced in the @theme block.

shadcn/ui

shadcn/ui provides copy-paste components rather than an npm package dependency. Components are installed into your codebase and can be customized as needed.

Configuration

The components.json file configures how shadcn/ui generates components:

components.json
{
  "style": "new-york",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui"
  }
}

Adding Components

Install components using the shadcn CLI:

bunx shadcn@latest add button
bunx shadcn@latest add card dialog form input

Components are installed to components/ui/ and can be imported:

import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardContent } from "@/components/ui/card";

The cn Utility

A utility function combines Tailwind classes with proper precedence handling:

lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Dark Mode

Dark mode is managed by Fumadocs' RootProvider which uses next-themes under the hood:

app/layout.tsx
import { RootProvider } from "fumadocs-ui/provider/next";

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={`${poppins.variable} ${jetbrainsMono.variable} antialiased`}>
        <RootProvider>{children}</RootProvider>
      </body>
    </html>
  );
}

The theme toggle in the documentation header switches between light and dark modes, with the purple brand background appearing in dark mode.

Color Reference

Light Mode

  • Background: White
  • Text: Dark purple (hsl(256, 45%, 14%))
  • Primary/Accents: Orange (hsl(33, 100%, 52%))
  • Cards: Near-white (hsl(0, 0%, 98%))

Dark Mode

  • Background: Deep purple (hsl(256, 63%, 8%))
  • Text: Light gray (hsl(0, 0%, 92%))
  • Primary/Accents: Bright orange (hsl(33, 100%, 55%))
  • Cards: Slightly lighter purple (hsl(256, 55%, 11%))

Usage Guidelines

When building UI components:

  1. Use the brand colors - bg-fd-primary for orange accents
  2. Test both themes - Toggle dark mode to verify components work
  3. Prefer shadcn components - Check if a component exists before building custom
  4. Use Poppins - The brand font is automatically applied via CSS variables

On this page