Casper Studios internal design system for generating consistent, production-grade SaaS UI. Use this skill whenever generating UI code for internal tools, client apps, dashboards, POCs, prototypes, or any visual interface — even quick mockups or artifacts. Apply it any time the output is a React component, page, or layout. If the user mentions "our design system", "Casper style", "match our look", or asks you to build any kind of app or interface, use this skill. Also trigger when restyling or theming existing UI to match Casper's visual language. This skill takes priority over generic frontend-design guidance.
A clean, elevated SaaS design system built on shadcn/ui, Tailwind CSS v4, and React (Vite). Every interface generated for Casper Studios — whether a client demo, internal tool, or quick prototype — must follow these rules to maintain a consistent, professional visual identity across the team.
Before generating any UI code, read this file completely and the reference files listed below. You MUST read the reference files — they contain rules and code examples that are required for correct output. Skipping them will produce incorrect, off-brand UI.
Required for EVERY project:
references/components.md
— ALWAYS read. Reusable pieces: stat cards, list items, filter bars, kanban boards, profile cards, product cards, activity feeds, toast notifications, form validation states. Required whenever building UI elements inside a layout.
references/theme.css
— ALWAYS read. Tailwind CSS v4 theme tokens. Copy this file into your project as-is.
assets/
— Contains Casper Studios logo SVGs in 4 variants (default, variant, mono-black, mono-white). Use the correct variant based on background color — see the Logo section below.
Required based on platform:
references/web-layouts.md
— MUST read when the project is a web application. Web-specific responsive rules + code examples: app shell, sidebar nav, dashboard grid, data table page, page header.
references/mobile.md
— MUST read when the project is a mobile application. Mobile-specific rules + code examples: device frame, top bar, bottom tab navigation, form patterns, pinned-button layout, list views, card stacks, full screen compositions, contextual actions (menus + bottom sheets).
Non-negotiable: Do not generate UI without reading the platform reference file first. If you are unsure whether the project is web or mobile, ask the user before proceeding.
Before You Generate: Required Context
Before producing any UI code, confirm the following with the user. If their prompt already answers these clearly, proceed without asking. If not, ask before generating anything.
Platform — Is this a web application or a mobile application? (Determines which reference file to follow.)
Dark mode — Should the interface support dark mode, or is it light mode only? (Determines whether to implement
.dark
class overrides, use
bg-neutral-0
for surfaces, and include the mono-white logo variant.)
Do NOT assume defaults for these. If the user says "build me a dashboard," you don't know if it's web or mobile, or if it needs dark mode. Ask.
Summary
The Casper aesthetic is clean authority — a professional SaaS style that feels premium without trying too hard. It uses generous whitespace, a restrained purple accent, and soft rounded surfaces to create interfaces that feel trustworthy and modern. Think Linear meets Notion: structured, breathable, quietly confident.
Core Principles
Whitespace is a feature. Generous padding, breathing room between sections. Never cram.
One accent, used sparingly. Brand purple (
#5900FF
) appears on active states, primary buttons, and key CTAs — nowhere else. If everything is purple, nothing is.
Rounded but not bubbly. 10px default radius for cards (shadcn default). Buttons and inputs use 8px. Feels modern without feeling like a toy.
Flat with depth hints. No heavy shadows. Use
shadow-sm
for cards,
shadow-md
for popovers. Never use
shadow-lg
on in-page elements.
Content over chrome. The UI should disappear. Users notice the data, not the design.
Tech Stack
React (Vite) with TypeScript
Tailwind CSS v4 — Use the theme file at
references/theme.css
as Casper brand overrides
shadcn/ui — Initialize a standard shadcn/ui project first (
shadcn init
), then layer Casper brand tokens from
references/theme.css
on top. The shadcn semantic layer (
bg-background
,
text-foreground
,
bg-primary
,
border-border
, etc.) is the base — Casper's theme.css adds brand colors, typography, shadows, and spacing on top of it, not as a replacement. Use shadcn components directly. Do NOT create custom base components that duplicate shadcn functionality
Lucide React — Icon library. Always use Lucide, never Heroicons or FontAwesome
Fonts —
DM Sans
with
sans-serif
as fallback for all UI text. Load via Google Fonts or bundle
Color System
The palette is intentionally restrained. Most of the UI is neutral gray + white, with purple as a sharp accent.
Usage Rules
Role
Token
Hex
When to use
Brand accent
brand-500
#5900FF
Active nav items, primary buttons, links, focus rings
Brand subtle
brand-50
#EEE5FF
Active nav background, selected row highlight, hover tints
Brand light
brand-100
#DECCFF
Icon circle backgrounds, soft tag fills
Default text
neutral-950
#0A0A0A
Page titles, headings
Body text
neutral-900
#171717
Primary body text
Subtext
neutral-500
#737373
Metadata, timestamps, secondary labels
Borders
neutral-200
#E5E5E5
Card borders, dividers, table lines
Surface
neutral-50
#FAFAFA
Page background behind cards
Card surface
neutral-0
#FFFFFF
Card backgrounds, panels (use
bg-neutral-0
for dark mode compatibility — see Dark Mode section)
Semantic Colors
Use these ONLY for status indicators, badges, and contextual feedback — never as decorative accents.
Success —
success-500
(
#22C55E
) for badges/icons,
success-50
for pill backgrounds
Error —
error-500
(
#EF4444
) for badges/icons,
error-50
for pill backgrounds
Warning —
warning-500
(
#F59E0B
) for badges/icons,
warning-50
for pill backgrounds
What NOT to do
Do NOT use brand purple for backgrounds on large surfaces
Do NOT use semantic colors decoratively
Do NOT introduce new colors. If you need a new shade, use the neutral scale
Do NOT use opacity-based colors when a token exists (e.g., don't do
text-black/50
, use
neutral-500
)
Typography
All text is set in DM Sans with sans-serif as fallback (
font-family: 'DM Sans', sans-serif
). Monospace (
font-mono
) is acceptable for code blocks, data labels, and IDs only.
Scale
Style
Size
Weight
Line Height
Use
Heading 1
30px
500
36px
Page titles only. One per view.
Heading 2
20px
500
24px
Section titles within a page
Heading 3
16px
500
20px
Card titles, subsection labels
Body
14px
400
20px
Default paragraph and UI text
Body Bold
14px
500
20px
Emphasis within body text, table headers
Caption
12px
400
16px
Timestamps, helper text, metadata
Caption Bold
12px
500
16px
Badge labels, small category tags
Rules
Headings are always
medium
weight (500), never bold (700)
NEVER use all-caps except for tiny metadata labels (e.g., "STATUS", "OWNER" in table column headers) set at caption size
Links use
brand-500
color with no underline by default, underline on hover
Do NOT vary font size beyond this scale. If something feels wrong, adjust spacing not font size
Spacing & Layout
Spacing Scale
Use Tailwind's default spacing scale. Key values:
4px
(p-1) — icon padding, tight gaps
8px
(p-2) — badge padding, small gaps
12px
(p-3) — input padding, card internal gaps
16px
(p-4) — card padding, section gaps
24px
(p-6) — between cards, content sections
32px
(p-8) — major section separation
48px
(p-12) — page-level padding on large screens
Layout Rules
Page background:
neutral-50
(
#FAFAFA
)
Content is always organized inside Cards (white background, border, rounded)
Maximum content width:
1280px
centered
On pages with a sidebar, sidebar is
240px
fixed width
Main content area uses remaining space with
24px
padding
Shadows & Elevation
The design is predominantly flat. Shadows are used to indicate layers, not to add decoration.
Token
Use
shadow-sm
Cards, inputs at rest
shadow-md
Dropdown menus, popovers, tooltips
shadow-lg
Modals, command palettes, overlays ONLY
shadow-overlay
Semantic alias for
shadow-lg
— identical value. Use on modals/sheets so the intent reads clearly in code
NEVER apply
shadow-lg
(or its alias
shadow-overlay
) to cards or in-page elements. These are reserved for floating layers only.
(10px) is the shadcn default. To make the entire UI sharper or rounder, change this single value — all tokens recalculate automatically.
Cards always use
rounded-lg
(10px). Buttons and inputs always use
rounded-md
(8px). Nested elements inside cards should use
rounded-sm
(6px) for non-interactive elements to maintain visual hierarchy — the inner radius should always be smaller than the outer.
Iconography
Use Lucide React exclusively. Import as:
import { IconName } from "lucide-react"
Default icon size:
16px
for inline,
20px
for standalone
Icon color follows its text context (e.g.,
neutral-500
for subtext,
brand-500
for active)
For icons inside circular backgrounds (common in lists and dashboards):
Circle:
40px
diameter,
brand-50
or
brand-100
background,
rounded-full
Icon:
20px
,
brand-500
color
For semantic contexts, swap to the matching semantic color pair (e.g.,
error-50
bg +
error-500
icon)
Casper Studios Logo
The Casper Studios logo has four variants stored in
assets/
. Use the correct variant based on the background it sits on:
Variant
File
When to use
Default (full color)
assets/logo-on-white-default.svg
Light/white backgrounds. Gradient icon + purple "CASPER" + dark "STUDIOS". This is the primary logo — use it whenever possible.
Variant (full color)
assets/logo-on-white-variant.svg
Light/white backgrounds when you want all-black text instead of purple + dark gray. Same gradient icon.
Mono Black
assets/logo-mono-black.svg
Light backgrounds where color is unavailable (e.g., print, grayscale contexts). Grayscale icon + near-black text.
Mono White
assets/logo-mono-white.svg
Dark backgrounds only. Gray icon + white text. Use this whenever the logo sits on a dark surface (dark nav bars, dark hero sections, overlays, dark mode).
Rules
Light mode (default): Use
logo-on-white-default.svg
or
logo-on-white-variant.svg
Dark mode or dark surfaces: Use
logo-mono-white.svg
— never place the default or black logo on a dark background
The logo should appear in the sidebar header (web) or top bar (mobile) at the sizes defined in those patterns
Do NOT resize the logo disproportionately — maintain the original aspect ratio
Do NOT place the logo on busy backgrounds or low-contrast surfaces. If contrast is insufficient, use the mono variant that provides the best visibility
Minimum clear space around the logo:
8px
on all sides
shadcn/ui Component Theming
Use shadcn/ui components as your base layer. Theme them using the CSS variables in
references/theme.css
. Here is how specific components should be configured:
Button
Primary:
brand-500
bg,
text-white
(literal white, not a token),
rounded-md
. Hover:
brand-600
.
Secondary: White bg,
neutral-200
border,
neutral-900
text,
rounded-md
. Hover:
neutral-50
bg.
Ghost: No bg, no border.
neutral-600
text,
rounded-md
. Hover:
neutral-100
bg.
Destructive:
error-500
bg,
text-white
,
rounded-md
.
All buttons:
rounded-md
(8px),
14px
font.
Default height:
48px
(
h-12
). Use this on standalone pages, forms, modals, and any top-level content area.
Compact height:
36px
(
h-9
). Use when the button lives inside a smaller container — cards, panels, table rows, sidebar nav, filter bars, inline actions. The tighter context calls for a tighter control.
— Stat Card, List Item Row, Filter Bar, Kanban Board, Profile/Discovery Card, Product Card, Activity Feed Item, Toast Notifications, Form Validation States
references/mobile.md
— Mobile rules, Device Frame Shell, Mobile Top Bar, Bottom Tab Navigation, Mobile Form Layout, Pinned Bottom Button, Mobile List View, Mobile Card Stack, Full Screen Composition, Contextual Actions
Web vs Mobile Context
Every project is either a web application or a mobile application — the layout approach is fundamentally different. You MUST have already read the appropriate platform reference file (as instructed at the top of this document) before reaching this point. If you haven't, stop and read it now:
Do NOT treat a mobile app as a responsive web page. Mobile apps render inside a device frame and follow entirely different navigation, spacing, and interaction patterns.
Image Placeholders
When no real image is available, use soft gradient mesh backgrounds — NOT gray boxes. These should feel like abstract art, not loading states.
to image containers. These are placeholders — they should look intentional and beautiful.
Transitions & Animations
Every state change should feel smooth and intentional — no hard cuts. This applies across both web and mobile:
Interactive elements (buttons, inputs, cards): Use
transition-colors
or
transition-all
with Tailwind's default duration (~150ms). State changes like hover, focus, and active should never feel instantaneous.
Page transitions: When navigating between views, apply a subtle slide or fade. A
150–300ms
ease-out transition keeps things feeling responsive without sluggish.
Modals, Dialogs, Sheets: Always animate in and out. Sheets slide from their edge (left, right, bottom), dialogs/modals fade + scale up slightly from ~95% to 100%. The overlay backdrop should fade in (
opacity 0→50%
), not appear instantly.
Lists and content loading: When new items appear (e.g., after a fetch), a subtle fade-in or stagger is preferred over a hard pop-in.
Nothing on screen should ever just "appear" or "disappear" — every visual change gets a brief, smooth transition.
Empty, Error & Loading States
Every screen has at least three faces: populated, empty, and broken. AI-generated interfaces almost always show only the happy path with fake data. A polished interface acknowledges the other two.
Empty State
When a list, table, or content area has no data yet (first-time use, zero results, cleared filters):
Center a Lucide icon (
48px
,
neutral-300
) + a short heading (
Heading 3
) + one line of body text (
neutral-500
) vertically in the content area
Optionally include a primary action button below the text (e.g., "Create your first project")
Do NOT show an empty table with headers and no rows — that looks like a bug, not a feature
Do NOT use illustrations or complex graphics. Keep it text + icon. Minimal.
Error State
When something goes wrong (network failure, permission denied, server error):
Same centered layout as empty state, but use
error-500
for the icon color
Icon: contextual —
WifiOff
for network errors,
ShieldX
for permission,
AlertTriangle
as a generic fallback
Heading: brief, human-readable ("Something went wrong", "Couldn't load projects")
Body: one sentence explaining what to do ("Check your connection and try again")
Include a "Retry" button (
secondary
variant) when the action is retryable
Loading State
When content is being fetched:
For initial page loads: Show a single centered spinner (
Loader2
icon with
animate-spin
,
24px
,
neutral-400
). No skeleton screens unless explicitly requested — they add complexity without clarifying the design
For inline updates (e.g., submitting a form, loading more items): Swap the trigger button's label to a spinner + "Loading…" and disable the button
For pull-to-refresh or lazy loading: A small spinner at the top or bottom of the list,
neutral-400
The anti-pattern rule "No animated skeletons or shimmer effects in static mockups" still stands — but a simple
animate-spin
on a loader icon is fine for interactive interfaces.
Form Validation & Error Feedback
Inputs need to clearly communicate when something is wrong. This applies to both web and mobile. For code examples, see the Form Validation States section in
references/components.md
.
Behavioral Rules
Validate on blur (when the user leaves the field), not on every keystroke — keystroke validation feels aggressive
Show errors inline, directly below the relevant field. Never collect errors at the top of the form — users shouldn't have to hunt
When the user corrects the input and the field is valid, remove the error state immediately (on change, not on blur)
If the form is submitted with errors, scroll to the first invalid field and focus it
Success Confirmation (Post-Submit)
After a successful form submission, provide clear feedback. Don't just silently navigate away:
Option A: Toast notification confirming the action + navigate to the next logical screen
Option B: Inline success message replacing the form content (useful for single-purpose screens like "Reset password")
Toast Notifications
Transient feedback messages that confirm actions, surface errors, or provide information. Use shadcn's
Sonner
toast component. For code examples and the full variant table, see the Toast Notification section in
references/components.md
.
Positioning & Behavior
Web: Bottom-right corner,
16px
from the edge
Mobile: Top-center, below the status bar / top bar area
Duration:
4000ms
default,
6000ms
for messages with an action link. Error toasts should persist until dismissed
Stacking: Max 3 visible at once. New toasts push older ones up (web) or down (mobile)
Animation: Slide in from the edge + fade. Slide out + fade on dismiss. Should feel like the Transitions & Animations section — smooth, never instant
Data Visualization Palette
When rendering charts (Recharts, Chart.js, etc.), use this ordered color sequence so data visualizations feel native to the brand. The palette starts with the brand purple and fans out through distinguishable hues:
Order
Name
Hex
Use
1
Brand
#5900FF
Primary data series, single-metric charts
2
Teal
#14B8A6
Secondary series
3
Amber
#F59E0B
Tertiary series
4
Rose
#F43F5E
Fourth series, or "negative" in comparisons
5
Sky
#0EA5E9
Fifth series
6
Lime
#84CC16
Sixth series
Rules
For single-metric charts (one bar, one line, one donut), always use
Brand
(
#5900FF
)
For two-series comparisons, use
Brand
+
Teal
Apply colors in order — don't skip or shuffle. Consistency across charts makes dashboards feel cohesive
Use
10%
opacity fills for area charts (e.g.,
#5900FF1A
for brand area fill)
Gridlines:
neutral-200
. Axis labels:
neutral-500
, caption size (12px). Axis lines:
neutral-300
Tooltips: White bg,
shadow-md
,
rounded-md
,
neutral-200
border — same treatment as popovers
Never use brand purple for "negative" or "declining" values — use Rose for that. Purple is always neutral-to-positive
Dark Mode
Dark mode is off by default. Only implement it when explicitly requested by the user or client. When dark mode is requested, follow these rules — do NOT improvise an inverted palette.
How It Works
The theme file (
references/theme.css
) includes a
.dark
class block that overrides CSS custom properties. Adding
class="dark"
to
<html>
or a wrapper element flips the entire color system without changing any component code. The neutral scale inverts so existing utilities (
text-neutral-900
,
bg-neutral-50
,
border-neutral-200
) automatically produce the correct dark-mode values.
Token Behavior in Dark Mode
You don't change token names in your code — the
.dark
override changes the values behind them. Here's what each token resolves to:
Token (unchanged in code)
Light Mode Value
Dark Mode Value
Notes
bg-neutral-50
#FAFAFA
(light gray)
#171717
(near-black)
Page background
bg-neutral-0
#FFFFFF
(white)
#0A0A0A
(near-black)
Card surfaces
border-neutral-200
#E5E5E5
(light gray)
#404040
(dark gray)
Borders / dividers
text-neutral-950
#0A0A0A
(near-black)
#FFFFFF
(white)
Page titles
text-neutral-900
#171717
(near-black)
#FAFAFA
(near-white)
Body text
text-neutral-500
#737373
(mid-gray)
#737373
(mid-gray)
Stays the same — mid-range
text-neutral-400
#A3A3A3
(light gray)
#A3A3A3
(stays same)
Subtext — readable on dark
bg-brand-500
#5900FF
#7A33FF
(lighter)
Brighter for contrast on dark bg
bg-brand-50
#EEE5FF
(light purple)
#120033
(very dark purple)
Tinted backgrounds invert
bg-white
vs
bg-neutral-0
— Critical Distinction
Tailwind's built-in
white
and
black
are NOT overridden in dark mode. They always resolve to literal
#FFFFFF
and
#000000
.
Use
text-white
when you mean actual white — e.g., button text on a
brand-500
background. This stays white in both modes. ✅
Use
bg-neutral-0
(not
bg-white
) for surfaces that should invert in dark mode — e.g., card backgrounds, sidebars, top bars.
bg-neutral-0
maps to
#FFFFFF
in light and
#0A0A0A
in dark. ✅
Use
bg-neutral-50
for page backgrounds. Maps to
#FAFAFA
in light,
#171717
in dark. ✅
If you use
bg-white
on a card, it will stay bright white in dark mode — blinding. Use
bg-neutral-0
instead.
Shadows in Dark Mode
Shadows are nearly invisible on dark surfaces. The
.dark
block increases shadow opacity so elevation is still perceptible, but cards should primarily rely on their
neutral-200
border (which maps to a subtle dark gray divider in dark mode) for definition.
Logo
Use
assets/logo-mono-white.svg
— the only variant approved for dark surfaces.
What NOT to Do in Dark Mode
Do NOT manually set dark colors in component code — rely on the
.dark
class override
Do NOT use
bg-white
for surfaces — use
bg-neutral-0
so they invert properly
Do NOT use
brand-500
for large tinted surfaces — use
brand-50
(which maps to a dark purple in dark mode)
Do NOT change spacing, radius, or typography — the spatial system is mode-independent
Anti-Patterns (Do NOT)
No gradients on buttons. Flat solid colors only.
No colored page backgrounds. Background is always
neutral-50
or
neutral-0
.
No heavy borders. Max
1px
for structural borders. Never 2px+.
No rounded-full on cards. Cards are
rounded-lg
(10px), never circles.
No custom fonts. DM Sans / sans-serif only. Monospace for code.
Prefer icon + label navigation on desktop. Icon-only sidebars are acceptable if a tooltip with the label appears on hover or focus.
No dark mode unless explicitly requested. Default is always light.
No animated skeletons or shimmer effects in static mockups.
No drop shadows on text. Ever.
No border-radius mixing. Don't use the same radius token at different hierarchy levels — e.g.,
rounded-lg
cards should contain
rounded-sm
children, not
rounded-lg
children. The inner radius should always be smaller than the outer.
No floating labels or placeholder-as-label. Labels must always be visible above inputs. Placeholders are hints, not labels — they vanish on focus and users lose context.
No emojis in the UI. Icons only. The only exception is user-generated content — if a user typed an emoji, display it. But never add emojis to labels, headings, buttons, nav items, placeholders, or any system-generated text.
Checklist Before Output
Before delivering any UI code, verify:
Uses
DM Sans
font family (with
sans-serif
fallback)
Brand purple only on interactive/active elements
Cards have
bg-neutral-0
+
neutral-200
border +
rounded-lg
+
shadow-sm
No unauthorized colors, fonts, or shadows
All icons from Lucide React
shadcn components used where available (not custom recreations)
Spacing feels generous — nothing cramped
Page background is
neutral-50
, not white
Empty states are handled (not just the happy path with fake data)
Form inputs show error styling on validation failure (red border + message below)
Actions provide feedback via toast or inline confirmation
Charts (if present) use the data visualization palette in order
All form inputs have visible external labels (not inside the input)
Placeholder text is de-emphasized (
neutral-400
, normal weight)
Casper Studios logo uses the correct variant for the background (default on light, mono-white on dark)
If dark mode was requested:
.dark
class applied, logo uses mono-white variant, surfaces use
bg-neutral-0
(not
bg-white
)
Border-radius: buttons/inputs use
rounded-md
(8px), cards use
rounded-lg
(10px), nav items/inner elements use
rounded-sm
(6px)
Button/input heights:
48px
(
h-12
) by default,
36px
(
h-9
) when inside cards, panels, tables, or other compact containers
Additional checks for Web Applications:
Sidebar follows the 240px / grouped nav / active state pattern