Text to image
Generate an image from a prompt, with optional reference images and a seed for reproducibility.
Sources
Sources
Documentation
The page shell for a documentation site: a persistent icon rail of product areas, a sectioned page nav beside it, a strip of announcements pinned above the page, and a content column measured for reading. It is a block, not a component — it owns arrangement and nothing else. The rail is B1 in its icon-rail configuration, the nav is B3, the strip is L3 at its quietest level, and the page body is built from A12 headings, K6 citations and A1 keycaps. Each of them keeps its own props, its own state model and its own accessibility contract.
OpenAI docs and Lovable docs both split navigation in two, and the split is the whole point: the rail answers which product am I in, the nav answers which page of it am I reading. Merging them into one list is the mistake this shell exists to prevent — a flat nav that mixes products with pages makes both questions harder, and it gets worse with every product you ship. The second decision worth copying is the measure. Documentation is long-form prose, and a column stretched to viewport width turns every line return into a small act of navigation; this is the one surface in the catalog where a max-width is not a preference.
Observed in: OpenAI docs · Lovable docs
data-region="icon-rail"B1 app-sidebar at icon width, holding the product areas. Switches product, never page.data-region="doc-nav"B3 sidebar-nav for the pages of the active area, or L1 when the area has none.data-region="announcement-strip"L3 feature-announcement, fixed at dismissible-chip level, pinned above the content.data-region="content-column"The scroll container. Named, focusable, and never itself measured — the article inside it is.docs-shellRoot. Contains its own fixed descendants so the shell can be embedded.docs-shell-areasThe rail's own nav landmark, named separately from the page nav.docs-shell-nav-headerThe rail toggle plus the A1 keycaps that advertise its keyboard binding.docs-shell-articleThe measured column. 68ch, on the article rather than the scroller.docs-shell-sectionOne section: an A12 heading, a body, and its citations.docs-shell-sourcesThe labelled run of K6 markers under a section.docs-shell-statusAt the top of the content column, only when status is passed. Holds M6 or the vendored Alert; the shell adds no live region of its own.shell-skeleton-regionOne per region while loading: hidden from assistive tech and sized like the loaded region. It carries data-loading-region, as does each loaded region's box, which is what the loading twin measures.Reach for it when your product ships more than one thing and each of them needs its own documentation. Keep the two navigation props apart: `areas` with `activeAreaId` and `onSelectArea` move between products, `navSections` with `activePageId` and `onSelectPage` move between pages of the product you are already in. Feed `navSections` from whichever area is active — the rail changing is what changes the nav. `announcements` is controlled the way L3 is: the shell emits an id and your host stores it, so pass `onDismissAnnouncement` or the dismiss control does nothing. Page bodies can be `sections`, which get A12 headings and K6 citations for free, or arbitrary `children` for MDX you already render; both land inside the measure. Pass `status` for a message about the whole surface (offline, reconnecting, a failed save, an expired session, a rate limit): it renders at the top of the content column, above the announcement strip, only when given, and holds M6 or the vendored Alert. Pass `loading` for first paint: every region draws a skeleton at its loaded size, the root is marked busy, and nothing is mounted that could take focus.
Do
Keep the rail and the nav as two named navigations doing two different jobs.
Let the content column stay measured, even when the window is wide.
Every image request is a POST to /v1/images with a JSON body. The response streams progress events until the final asset URL arrives, so a client can show partial results without polling for them.
Mount the command palette once, at the root of your app, and keep it out of the shell: it is not a shell slot, and a palette mounted in each shell binds its shortcut once per surface.
Don't
Don't flatten products and pages into one list — both questions get harder, and it scales badly.
Don't stretch prose to the viewport; long lines are where readers lose their place.
Every image request is a POST to /v1/images with a JSON body. The response streams progress events until the final asset URL arrives, so a client can show partial results without polling for them.
fixed inset-y-0 h-svh, which is right only when the shell owns the viewport. The root sets contain: layout so the shell stays embeddable in a preview, a panel or an app region — if you re-wrap or restyle the root, keep that containment or the rail will pin itself to the browser's left edge. The root also constrains that container to the shell's own height, which is what makes railFooter usable in an embedded shell.nav slot, and the rail's nav landmark is a wrapper the shell adds. That wrapper is what gives the rail a name distinct from the page nav; drop it and the two navigations become indistinguishable to a screen reader even though they look different on screen.onDismissAnnouncement gives you a ✕ that visibly does nothing, which is worse than no ✕ at all. Feed announcements[].dismissed back from wherever you record it.bg-muted, and text-muted-foreground on that surface measures 4.34:1 against a 4.5:1 minimum. The shell rebinds --muted-foreground on the strip rather than restyling slots, because composed children carry their own muted classes and no className from here can reach them. If you re-tint the strip, move the rebind with the tint.bg-accent, so painting the column the same token would erase the very affordance that says which page you are on — and B3's row icons and A12's small headings both carry text-muted-foreground, which fails contrast on any of the three muted surfaces.action renders inside the A12 header, and the shell promotes that header to a level-2 heading — so whatever you put there becomes part of the heading's accessible name. Keep it to two or three words; a full sentence turns every heading in the page outline into a paragraph.tabIndex={0} and an accessible name taken from the page title. Moving the overflow onto the article without moving those two with it fails axe's scrollable-region-focusable rule and strands keyboard users outside the page they navigated to.loading, B1 is not mounted, because it always renders its rail button and a loading shell mounts nothing to click. The rail skeleton takes its width from the sidebar provider's state instead, so it follows defaultRailExpanded and a Cmd/Ctrl+B toggle, but railBrand and railFooter appear only once loading ends.announcements has an undismissed entry while loading. An announcement that arrives with the data pushes the content column down by one strip.npx shadcn@latest add https://super-ai-components.vercel.app/r/docs-shell.jsonRequires a shadcn app on Base UI (style base-nova). Radix-based styles such as new-york fail to typecheck: these components use Base UI's render prop and callbacks like onOpenChangeComplete, which Radix primitives do not have.