Guides building SaaS dashboards, settings pages, data tables, and layouts using patterns for component selection (shadcn/ui + Tailwind), page composition, responsive design, dark mode, and UI states (loading, empty, error).
npx claudepluginhub whawkinsiv/solo-founder-superpowers --plugin solo-founder-superpowersThis skill uses the workspace's default tool permissions.
This skill covers how to build pages and compose components for SaaS applications. It provides the structural decisions — which components to use, how to lay out pages, and how to handle every UI state. For visual styling (colors, typography, spacing systems), see the beautify skill. For user flow design, see ux-design. For accessibility requirements (semantic HTML, keyboard nav, ARIA), see ux-...
Provides Ktor server patterns for routing DSL, plugins (auth, CORS, serialization), Koin DI, WebSockets, services, and testApplication testing.
Conducts multi-source web research with firecrawl and exa MCPs: searches, scrapes pages, synthesizes cited reports. For deep dives, competitive analysis, tech evaluations, or due diligence.
Provides demand forecasting, safety stock optimization, replenishment planning, and promotional lift estimation for multi-location retailers managing 300-800 SKUs.
This skill covers how to build pages and compose components for SaaS applications. It provides the structural decisions — which components to use, how to lay out pages, and how to handle every UI state. For visual styling (colors, typography, spacing systems), see the beautify skill. For user flow design, see ux-design. For accessibility requirements (semantic HTML, keyboard nav, ARIA), see ux-design/ACCESSIBILITY.md.
Default: shadcn/ui + Tailwind CSS. This is the correct choice for 90% of founder-built SaaS apps. shadcn/ui provides copy-paste components built on Radix primitives — you own the code, can customize freely, and avoid dependency lock-in.
Decision framework:
| Situation | Choice | Why |
|---|---|---|
| Public-facing SaaS product | shadcn/ui + Tailwind | Full design control, great defaults, accessible |
| Internal admin tool / back-office | Ant Design or MUI | Dense data display, built-in table/form patterns, faster to ship |
| Need one specific primitive (tooltip, dialog) | Radix directly | Skip the styling layer, use just the behavior |
| Existing MUI/Chakra codebase | Stay with current lib | Migration cost rarely worth it mid-project |
| React Native / mobile app | React Native Paper or Tamagui | Web component libs don't apply |
When using shadcn/ui: install components individually (npx shadcn-ui@latest add button), don't install everything upfront. Configure the components.json path aliases to match the project structure.
Every SaaS page follows a predictable anatomy. Structure pages using these content zones:
Page header — Title, description, primary action button (top-right). Breadcrumbs above the title if nested. Height: 64-80px.
Primary content — The main purpose of the page. Takes 60-70% of width on desktop. This is the "spine" — the vertical column the user's eye follows.
Secondary content — Supporting information, filters, related items. Sidebar or below primary content. Takes 30-40% width or full-width below on mobile.
Actions area — Sticky bottom bar for forms, floating action button, or inline with content. Never hide primary actions in menus.
The page spine principle: every page has one dominant vertical column. On a dashboard, it's the metrics-to-activity flow. On a settings page, it's the form sections. On a list page, it's the table. Identify the spine and give it visual priority — other content supports it.
Standard page container: max-w-7xl mx-auto px-4 sm:px-6 lg:px-8. For content-heavy pages (docs, settings forms): max-w-3xl. For full-bleed layouts (dashboards): no max-width, use the sidebar to constrain.
Every data-driven component must handle five states. Missing any one of these creates a broken experience. When building any component that displays dynamic data, implement all five:
Use skeleton loaders that match the shape of the real content. Never use spinners for inline content — spinners are only for full-page loads or button actions.
animate-pulse with bg-muted blocks. Match heights and widths to actual content.The most neglected state. Every empty state needs: an icon or illustration, a headline explaining what will appear here, a description of how to populate it, and a primary CTA button.
No projects yet → "Create your first project" button
No search results → "Try different keywords" suggestion
No notifications → "You're all caught up" message (no CTA needed)
Empty states are onboarding moments. Use them to teach users what to do next.
Display what went wrong in plain language and provide a retry action. Never show raw error codes or stack traces.
The normal content display. This is what most developers build first (and sometimes only). Ensure it handles variable content lengths — long titles, missing optional fields, various image aspect ratios.
What happens when there's too much data:
line-clamp-2 or line-clamp-3Use mobile-first with Tailwind's breakpoint system. Default styles target mobile, then layer on complexity:
| Breakpoint | Width | Target |
|---|---|---|
| default | 0-639px | Mobile phones |
sm | 640px+ | Large phones, small tablets |
md | 768px+ | Tablets |
lg | 1024px+ | Small laptops, landscape tablets |
xl | 1280px+ | Desktops |
2xl | 1536px+ | Large monitors |
What changes at each breakpoint:
Key responsive patterns:
| Desktop Pattern | Mobile Adaptation |
|---|---|
| Sidebar navigation | Bottom tab bar (5 items max) or hamburger menu |
| Data table | Stacked cards with key fields visible |
| Multi-column grid | Single column, stacked |
| Side-by-side panels | Tabbed or stacked sections |
| Hover tooltips | Tap-to-reveal or always-visible labels |
| Right-click context menu | Long-press menu or action row with icons |
| Modal dialog | Full-screen sheet sliding up from bottom |
For the sidebar-to-bottom-nav pattern: don't just hide the sidebar. Build a dedicated BottomNav component with 4-5 icon+label items. The items should map to the top-level sidebar sections, not be a 1:1 copy.
When to implement: If building an MVP for validation, skip dark mode. If building a product users will spend hours in (dashboards, dev tools, writing apps), implement from day 1. Adding dark mode later requires touching every component — it's significantly cheaper to build it in from the start.
Implementation approach:
:root and .darkdark: prefix for overridesglobals.css<html> element: document.documentElement.classList.toggle('dark')prefers-color-scheme media query, but let users overridelocalStorageWhat changes beyond swapping background/text colors:
border-border (which maps to a lighter value in dark mode) rather than hardcoded colors.brightness-90). Avoid pure-white images on dark backgrounds.hover:bg-muted rather than hardcoded opacity changes. Light mode hovers darken, dark mode hovers lighten.| Mistake | What to do instead |
|---|---|
| Building a custom component library from scratch | Use shadcn/ui. Customize after shipping. |
| Only implementing the success state | Build all five states (loading, empty, error, success, overflow) for every data component |
| Using spinners everywhere | Skeleton loaders for inline content, spinners only for page-level or button loading |
| Fixed layouts that don't respond to screen size | Mobile-first responsive with Tailwind breakpoints, test at 375px width |
| Hiding critical actions in dropdown menus | Primary actions get dedicated buttons, only secondary/tertiary go in menus |
| Pixel-perfect custom breakpoints | Use Tailwind's standard breakpoints — they cover real devices |
| Dark mode as an afterthought | Decide at project start: implement now or explicitly defer. Don't half-implement. |
| Tables on mobile with horizontal scroll only | Transform tables into card layouts on mobile, showing key data points |
| Modals on top of modals | One modal at a time. Use sheets/slide-outs for secondary actions from a modal. |
| Inconsistent spacing between sections | Pick a page section gap and stick with it: space-y-8 for page sections, space-y-4 within sections |