Cal.comdesign-system

Settings page layout

How Cal.com composes authenticated settings pages — shell, AppHeader, and stacked section frames.

Settings pages are the highest-volume authenticated surface in Cal.com. Almost every route under apps/web/app/(use-page-wrapper)/settings/ follows the same recipe: SettingsShell provides secondary navigation, then the page content column is AppHeader + stacked CardFrame (or list) sections.

Field wiring, validation, and save footers are covered in Forms. This Guide is the page chrome around those cards.

Product UI detail for the header composite: App Header.

Anatomy

  1. SettingsShell — route-group layout (settings/(settings-layout)/layout.tsx → SettingsShellServer). Secondary nav + content column. Do not re-implement this in a page.
  2. AppHeader — page title (AppHeaderContent title=…), optional description, optional actions.
  3. Body — one or more sections in a vertical stack (flex flex-col gap-4), usually CardFrame + form cards; sometimes a list or table under the header.
  4. page.tsx contract — session, data fetching, and permission checks live in the App Router page. UI often lives in ~/modules/settings/.... Prefer fail-closed notFound() when the caller lacks permission (never invent a “no access” empty in layout.tsx).

Best practices

Header · title and description

Most settings views use title + description with no actions.

apps/web · settings AppHeader

General

Manage settings for your language and timezone

apps/web — general settings (pattern)

AppHeaderContent requires the title prop — do not put the heading only as children.

Header · with actions

Put primary create / add CTAs in AppHeaderActions. Hide the action when it does not apply (empty list CTAs often live in the empty state instead — see Empty states).

apps/web · settings AppHeader + actions

Out of office

Schedule absences and automatic booking redirects

apps/web — out of office (pattern)

The same shape appears with the CTA gated by PBAC on org/team roles pages (canCreate → AppHeaderActions).

Full page recipe

Under the shell content column: header, then stacked cards. Forms use Form className="contents" so the card chrome owns the layout — details in Forms → Settings page card.

apps/web · settings page recipe

General

Manage settings for your language and timezone

apps/web — general settings page (pattern)

Where AppHeader lives

Both placements ship today — pick one and keep the page thin:

Do not put permission checks in layout.tsx. Authorize in page.tsx, then render header + body (or notFound()).

apps/web — roles page (pattern)

Not this Guide

Call sites

Example URLs

Not exhaustive — a few places you can open locally (e.g. http://localhost:3000) to see this in product.