designComponents

Search the design system…

Search the design system…

layouts

SettingsShell

Canonical /settings layout — centered focused-mode container with a sticky 256px sidebar and a width-capped content column. Use for every Profile / Security / Billing / Organisations / API-tokens screen across OAPPS products.

Settings in OAPPS products is a focused view, not an app sub-page. The shell renders on a clean canvas — the global app sidebar and product chrome stay above the route boundary. SettingsShell owns its own H1 title and centers a two-column body: a 256-wide vertical nav on the left (<SettingsSidebar>) and a `max-w-2xl` content column on the right. Inside the content column every sub-page composes from a tiny set of primitives: <SettingsPageHeader> for the top, <Separator />, then either a stack of <SettingsSectionCard> rows (feature toggles / management entry points), a regular form for editable fields, or — when the sub-page drills further into a managed resource — a <SettingsSubPageHeader> (with built-in '← Back') followed by a <SettingsResourceTable> (typed compact table with empty-state baked in). This is the same shape used by Linear, Stripe, Vercel, and Docufly settings — there is no creative space here, agents copy the recipe.

Install

Import from the workspace package:

import { SettingsShell, SettingsSidebar, SettingsPageHeader, SettingsSubPageHeader, SettingsSectionCard, SettingsResourceTable, SettingsSidebarEntry, SettingsResourceTableColumn } from "@8maverik8/design";

Examples

Four canonical recipes — Profile, Organisations, Security, Passkeys

Click the rail on the left to switch between sub-pages — each one shows a canonical recipe. **Profile** = editable form ending with a destructive 'Delete account' row. **Organisations** = top-right action + bordered table. **Security** = stack of <SettingsSectionCard> rows, the most common Settings shape. **Passkeys** (click 'Manage passkeys' inside Security) = drill-down sub-view built from <SettingsSubPageHeader> (with '← Back to security' + an 'Add passkey' action) and <SettingsResourceTable> (typed compact table with empty state baked in). In a real product the nav also includes API Tokens / Webhooks / Public Profile / Billing — every one of them follows one of these four shapes.

Settings

Security

Here you can manage your password and security settings.

Two factor authentication

Add an authenticator app as a secondary authentication method for signing documents.

Passkeys

Allows authenticating using biometrics, password managers, hardware keys, etc.

Recent activity

View all recent security activity related to your account.

Active sessions

View and manage all active sessions for your account.

Linked accounts

View and manage all login methods linked to your account.

// app/(settings)/settings/layout.tsx — focused mode, no global chrome.
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import { Shield, User, Users, /* … */ } from "lucide-react";
import {
  SettingsShell,
  SettingsSidebar,
  type SettingsSidebarEntry,
} from "@8maverik8/design";

const nav: SettingsSidebarEntry[] = [
  { href: "/settings/profile",       label: "Profile",       icon: User   },
  { href: "/settings/organisations", label: "Organisations", icon: Users  },
  { href: "/settings/security",      label: "Security",      icon: Shield },
];

export default function SettingsLayout({ children }: { children: React.ReactNode }) {
  const pathname = usePathname();
  return (
    <SettingsShell
      sidebar={
        <SettingsSidebar
          nav={nav}
          activeHref={pathname}
          renderLink={({ href, className, children }) => (
            <Link href={href} className={className}>{children}</Link>
          )}
        />
      }
    >
      {children}
    </SettingsShell>
  );
}

// app/(settings)/settings/security/page.tsx — canonical "rows of actions" recipe.
import { Button, Separator, SettingsPageHeader, SettingsSectionCard } from "@8maverik8/design";

export default function SecurityPage() {
  return (
    <div className="space-y-8">
      <SettingsPageHeader
        title="Security"
        description="Here you can manage your password and security settings."
      />
      <Separator />

      <div className="space-y-4">
        <SettingsSectionCard
          title="Two factor authentication"
          description="Add an authenticator app as a secondary authentication method for signing documents."
          action={<Button size="sm">Enable 2FA</Button>}
        />
        <SettingsSectionCard
          title="Passkeys"
          description="Allows authenticating using biometrics, password managers, hardware keys, etc."
          action={<Button variant="outline" size="sm">Manage passkeys</Button>}
        />
        <SettingsSectionCard
          title="Active sessions"
          description="View and manage all active sessions for your account."
          action={<Button variant="outline" size="sm">Manage sessions</Button>}
        />
      </div>
    </div>
  );
}

Variants

SettingsSidebarEntry

  • item(default)Flat row: `{ href, label, icon }`. Icon is rendered to the left of the label. Active row hits `bg-muted text-foreground`.
  • groupParent label with nested items: `{ label, icon, items: SettingsSidebarItem[] }`. Parent acts as a header that links to the group's first child; children render indented (no icons) underneath.

SettingsSectionCard.action — Button variant

  • default(default)Primary positive action — 'Enable 2FA', 'Save', 'Create'. Solid button on the card's right edge.
  • outlineNavigate-deeper actions — 'Manage passkeys', 'View activity', 'Manage sessions'. The card is a routing affordance, not a state toggle.
  • destructiveGenuinely destructive actions — 'Disable 2FA', 'Delete account', 'Revoke token'. NOT 'Sign out everywhere else' — that's `outline`.

Anatomy

  • SettingsShellOuter layout. Centered `max-w-6xl` page with px-6/py-10, owns the H1 'Settings' title, renders the sidebar slot next to a `max-w-2xl` content column. Don't wrap it in another container, don't set additional max-widths.
  • SettingsSidebarVertical nav inside the sidebar slot. Takes `nav: SettingsSidebarEntry[]` — each entry is either a flat `{href, label, icon}` item or a group `{label, icon, items}` with sub-items. Router-agnostic via the `renderLink` slot (pass `next/link`'s Link for Next.js).
  • SettingsPageHeaderTop of every sub-page. h2 + description + optional right-aligned single action (e.g. 'Create organisation'). Always followed by a <Separator />.
  • SettingsSubPageHeaderTop of an inner sub-view that's drilled in from a section card (Manage passkeys, Manage sessions, View activity). Renders '← Back to {parent}' on top, then h2 + description + optional right-aligned primary action (e.g. 'Add passkey'). Always followed by a <Separator />.
  • SettingsSectionCardBordered horizontal row used for 'feature + one action' surfaces (Two factor authentication / Passkeys / Active sessions / Delete account). Title + description on the left, single <Button size='sm'> on the right.
  • SettingsResourceTableCompact card-wrapped table for 'managed resource lists' inside sub-views (passkeys, active sessions, API tokens, webhooks, linked accounts). Typed columns + rows + rowId + emptyText. Empty state is built-in — don't render your own fallback row. Action column ('Revoke', 'Sign out', 'Delete') gets `className: 'w-1 whitespace-nowrap text-right'` to hug content.

Guidelines

  • Use <SettingsShell> for every Profile / Security / Billing / Organisations / API-tokens / Webhooks / Public-profile screen across all OAPPS products. Same nav slot shape, same recipe inside, same canonical look.
  • Render the shell on a clean route boundary so the global app sidebar / top-level navigation stays above it. In Next.js: place SettingsShell in `app/(settings)/settings/layout.tsx` outside the `(app)` group that holds the global chrome.
  • Compose every sub-page as <SettingsPageHeader> + <Separator /> + a stack of <SettingsSectionCard> rows or a form. Don't invent alternate layouts.
  • Pass `next/link`'s `Link` as `renderLink` in Next.js — the sidebar stays router-agnostic and SPA navigations don't reload.
  • Wrap <SettingsShell> in another <PageContainer> or set extra `max-w-*` classes outside it.The shell already centers and caps the page (`mx-auto max-w-6xl px-6 py-10`). An outer wrapper double-margins it and breaks alignment with the H1.
  • Render the global app sidebar / top nav around the Settings flow.Settings is a focused mode. Showing the app chrome here invites confusion ('am I inside or outside the app?') and forces the content column to compete for width. The Docufly / Linear / Stripe pattern explicitly drops global chrome on Settings — we do the same.
  • Hand-roll <div className='flex border …'> for a 'title + button on the right' row.Every consumer drifts on padding / border-radius / vertical alignment. Use <SettingsSectionCard> — it owns those values, agents can't drift.
  • Stretch buttons inside <SettingsSectionCard> to full width or use `variant='destructive'` for navigate-deeper actions like 'Sign out everywhere else' or 'Manage sessions'.Destructive variant is loud red. It exists for irreversible actions (delete, disable). Misusing it desensitises users — the next real destructive button reads as just another button.
  • Drop ad-hoc input widths on form fields inside the content column.Content column is already capped at `max-w-2xl`. Inputs should be full-width (the default) — fixing them at 280px or 480px leaves an awkward gap to the column edge.