# Branding, UI and dashboard customization

URL: https://ship-and-scale.vercel.app/docs/build/customization

Change the product identity and navigation while preserving the shared design and access boundaries.

## Start with product identity [#start-with-product-identity]

Set `APP_NAME` for the configured name, then review `config/site.ts` and `config/marketing.ts`. The former supplies a default description and server-consumed name. It does not rewrite all sample headings, legal text, illustrations or email subjects. Search the source for sample product names before launch, excluding environments, dependencies and generated files.

```sh
rg -n 'Forma|starter|sample' app components config content emails
```

Review each occurrence rather than doing an unrestricted replacement. Some references explain the starter to developers; others are customer-facing copy that should describe your product. Keep credentials and private setup state out of search output and screenshots.

## Logo, favicon and social identity [#logo-favicon-and-social-identity]

`components/brand.tsx` renders the shared brand component. Replace or adapt its mark in that component; store approved public assets in `public/`. Change `app/favicon.ico` for browser tabs and the social image referenced by `config/seo.ts`. The [SEO metadata guide](/docs/website/seo-metadata) explains canonical origins, page titles and sharing previews.

Workspace logos and personal avatars are user data, not public brand assets. Do not copy one customer's logo into the public directory as a global product icon. Their authenticated access paths are documented under [Storage](/docs/storage/overview).

## Preserve the neutral visual foundation [#preserve-the-neutral-visual-foundation]

The installed shadcn preset is Base UI/Vega with neutral colors, Inter and Lucide. All 61 UI component files were intentionally installed. Use the existing primitives in `components/ui` and the semantic CSS variables in `app/globals.css`.

For a subtle radius adjustment, edit the shared `--radius` token and inspect cards, inputs, dialogs and buttons together. For a color adjustment, review both `:root` and `.dark` definitions of the relevant semantic pair. For example, a primary background needs a readable primary foreground. Avoid hardcoding a light background in one component that becomes unreadable in dark mode.

```css title="Example token change in app/globals.css"
:root {
  --radius: 0.75rem;
}
```

This small example changes radius only. Keep the rest of the existing token declarations in place; do not replace the entire stylesheet with it. The documentation uses its own scoped `app/docs/docs.css` layered over shared tokens.

## Add a protected page [#add-a-protected-page]

Create `app/dashboard/reports/page.tsx` for a product-specific page. This minimal example displays the verified selected workspace; it does not claim a shipped reporting backend:

```tsx title="app/dashboard/reports/page.tsx"
import { getWorkspace } from '@/lib/auth';

export default async function ReportsPage() {
  const { organization } = await getWorkspace();
  return (
    <section className="grid gap-3 p-6">
      <h1 className="text-2xl font-semibold">Reports</h1>
      <p>Reports for {organization.name}.</p>
      <p>No reports have been created yet.</p>
    </section>
  );
}
```

The dashboard layout already establishes the application shell and onboarding flow. `getWorkspace()` confirms current account/workspace access for the page's data. Add actual data access through user-scoped clients and database policies when the feature becomes functional; a protected route alone does not isolate records.

## Add the navigation item [#add-the-navigation-item]

The sidebar entries live in `components/dashboard/app-shell.tsx`. Import the chosen Lucide icon, then add an item beside the existing entries with `href`, `label` and `icon`:

```tsx
{ href: '/dashboard/reports', label: 'Reports', icon: FileText }
```

Import `FileText` from `lucide-react` in the same file. Verify its route exists, the active state is correct and the collapsed sidebar still exposes an accessible name. Use [workspace notes](/docs/build/first-feature) for a complete create/list feature rather than leaving a sample page as a finished product claim.

## Loading, error and empty states [#loading-error-and-empty-states]

A loading state should say what is loading. An empty state should explain the user's next permitted action. An error state should preserve entered data when possible and give a retry path. Avoid passing raw database/provider error payloads into customer UI.

Use route `loading.tsx` and client `error.tsx` boundaries for data-loading transitions. Handle validation failures as inline form state instead of throwing every rejected input into a route-wide error boundary. Disable duplicate submission while pending, but keep server/database retry protection too.

## Responsive and keyboard checks [#responsive-and-keyboard-checks]

Inspect desktop and narrow mobile layouts, including the sidebar drawer, long workspace names, dialogs and tables. Check 320px and 390px widths for page-level horizontal overflow. Code blocks can scroll inside their container; the entire page should not require sideways navigation.

Use keyboard Tab and Shift+Tab, open dialogs and return focus to the invoking control. Inputs need labels and validation messages. Do not rely on an icon's shape or color alone to communicate state. Honor reduced-motion preferences if adding animation.

## Verify branding across surfaces [#verify-branding-across-surfaces]

Check the public homepage, login, dashboard, account menu, email templates, product docs, metadata and social image. Some branding is rendered server-side from `APP_NAME`; a Client Component should receive necessary public text through explicit props or an intentional public config, not import a secret-reading server module.

Run `npm run typecheck`, scoped lint and `npm run build`. Inspect changed UI in light/dark and mobile/desktop. These checks validate local presentation; they do not show a received email with the new brand. Complete that delivery check through [email templates](/docs/email/templates-delivery) when an authorized sender and recipient are available.