← LeaderboardThe Skill Leaderboard

app-shell

The frame of a web or mobile app: navigation (sidebar, top bar or tab bar), account menu and one empty work area, with the navigation every other screen will reuse.

Published by feimacode ↗feimacode/open-design-agent-kit

Try your first task

A source-grounded starter task has not been verified for this skill yet.

SKILL.md

name: app-shellzh_name: "应用框架"en_name: "App Shell"emoji: "🧭"description: "The frame of a web or mobile app: navigation (sidebar, top bar or tab bar), account menu and one empty work area, with the navigation every other screen will reuse."en_description: "The frame of a web or mobile app: navigation (sidebar, top bar or tab bar), account menu and one empty work area, with the navigation every other screen will reuse."category: appscenario: designtags: ["app shell", "navigation", "sidebar", "tab bar", "layout", "app frame", "应用框架", "导航"]triggers:   - "app shell"  - "app layout"  - "app navigation"  - "sidebar layout"  - "tab bar"  - "navigation bar"od:   mode: prototype  craft:     requires:       - product-ui      - state-coverage      - action-states      - ux-writing  surface: web  platform: desktop  scenario: design  preview:     type: html    entry: index.html  design_system:     requires: true  example_prompt: "The app shell for a project-management web app: sidebar with Projects, Tasks, Inbox, Reports and Settings, an account menu, and an empty Projects page."

App Shell

Produce the frame every screen of the app sits in: the navigation, the account menu and one work area, plus its empty state. Later screens reuse this navigation exactly, so get the item names and order right.

Workflow

  1. Name the places. From the brief, list three to seven top-level places in the order users need them. Nouns, not verbs ("Invoices", not "Manage invoices"). Settings and help go last or in the account menu.
  2. Pick the shell for the platform: sidebar (web app, desktop), top bar (simple web app with three or four places), bottom tab bar (phone, three to five places), icon rail (tablet).
  3. Mark the current place with aria-current="page" and a visible treatment, not colour alone.
  4. Account menu in the sidebar footer (web) or behind the avatar in the top bar (phone): name, workspace or plan, Settings, Sign out.
  5. The work area shows the first place's page header and its empty state (data-od-state="empty" visible as the default here is fine for a brand-new app; say so in the header copy).
  6. Shared navigation. If the brief has a flow plan, write the navigation exactly as its shared navigation lists it.

States

default (the first place with a little content) and empty (first use).

Self-check

  • Every item has a text label; icons are optional and come from one icon style.
  • At 390 px the sidebar becomes a top bar or bottom tab bar; nothing scrolls sideways.
  • The navigation would make sense on every screen of this app, not just this one.

Markup contract (shared by every app screen)

  • States. Each state of the screen is a sibling <section data-od-state="…">: default (populated), and the ones this pattern lists below from empty, loading, error, success, no-access, long-content. Only default is visible; the others carry hidden. The kit's check renders every state, and the preview shows them side by side.
  • Slots. Keep the seed's data-od-slot names (nav, toolbar, content, empty, loading) so states, links and comments line up.
  • Links between screens. When this screen is part of a planned flow, link to another screen with its entry path in href and its screen id in data-od-goto, and use the shared navigation the brief gives instead of drawing your own.
  • The app's own components. When the brief lists the app's components, build each part from one of them and mark it data-od-component="<Name>"; mark a part with no match data-od-component="new:<Name>".
  • Sample data. Records come from the data file the brief describes, never typed into the markup row by row: one template row or card with data-od-repeat="<list>" and data-od-bind=".<field>" inside it, single values with data-od-bind="<list>[n].<field>", and data-od-format for money, dates and numbers. Every screen that shows the same record binds to the same one.
  • Tokens only. Every colour, size, radius and shadow comes from the design system's :root variables. No raw hex outside :root.
  • Platform. Web app: the shell as given. iPhone, Android, tablet or desktop app: put data-od-device on <body> (iphone, android, tablet with data-od-orientation, or desktop-app with data-od-window="mac" or "windows"); the kit draws the device frame, status bar and home indicator around the screen, so never draw them yourself. iPhone or Android: a single column 390 px (iPhone) or 412 px (Android) wide, with a bottom tab bar instead of the side nav, a top bar with the title and a back button on inner screens, and 44 px (iPhone) or 48 px (Android) touch targets. Android screens use Material patterns (top app bar, floating action button where there is one main create action). Tablet: the sidebar collapses to an icon rail with labels in a tooltip, content in two columns where the pattern has a detail pane.
  • Density. Use the density the brief gives (compact, medium or airy) with the numbers from the product UI craft rules.

Seed

Start from this shell (one HTML file, inline CSS). Replace the comments; keep the structure.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Screen title · Product</title>
<style>
/* Paste the active design system's :root block here, then use only var(--…). */
*,*::before,*::after{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--fg);font:var(--text-base)/var(--leading-body) var(--font-body)}
.app{display:grid;grid-template-columns:1fr;min-height:100dvh}
.app-nav{display:flex;gap:var(--space-1);padding:var(--space-2) var(--space-4);border-bottom:1px solid var(--border);overflow-x:auto}
.app-nav a{display:flex;align-items:center;gap:var(--space-2);min-height:44px;padding:0 var(--space-3);border-radius:var(--radius-md);color:var(--muted);text-decoration:none;white-space:nowrap}
.app-nav a[aria-current="page"]{background:var(--surface);color:var(--fg);box-shadow:var(--elev-ring)}
.app-main{min-width:0;padding:var(--space-4)}
.app-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3);margin-bottom:var(--space-4)}
.app-toolbar h1{margin:0 auto 0 0;font:600 var(--text-xl)/var(--leading-tight) var(--font-display)}
[data-od-state][hidden]{display:none}
@media (min-width:64rem){
  .app{grid-template-columns:260px 1fr}
  .app-nav{flex-direction:column;border-bottom:0;border-right:1px solid var(--border);padding:var(--space-4)}
  .app-main{padding:var(--space-6) var(--space-8)}
}
</style>
</head>
<body>
<div class="app">
  <nav class="app-nav" aria-label="Main" data-od-slot="nav">
    <a href="#" aria-current="page">Current place</a>
    <a href="#">Another place</a>
  </nav>
  <main class="app-main">
    <header class="app-toolbar" data-od-slot="toolbar">
      <h1>Screen title</h1>
    </header>
    <section data-od-state="default" data-od-slot="content"><!-- the populated screen --></section>
    <section data-od-state="empty" data-od-slot="empty" hidden><!-- what will be here, why it is empty, the first action --></section>
    <section data-od-state="loading" data-od-slot="loading" hidden aria-busy="true"><!-- a skeleton shaped like the content --></section>
  </main>
</div>
</body>
</html>