VISUALLIdesign system

Responsive web app

One web app for desktop, tablet and phone browsers. Developers build it once with AppShell; the shell and its components rearrange themselves by the width they're given, so there is no phone variant to build or keep in sync. Feature VI-017 · PRODUCT. (The native mobile app is a separate surface with its own React Native layout.)

Three layouts

Width of the shell Layout Vault Actions Chat dock Give feedback Peeks, Go deeper, Sources
≥ 1024 wide Docked left, open Toolbar top-right, with labels Bottom-center, clear of the Vault Floating bottom-right Card; side panel on the right
720 – 1023 medium Drawer over the map, closed (☰ top-left) Toolbar, icons only (counts as badges) Bottom-center Floating bottom-right Card; sheet below 900px
< 720 compact Bottom sheet (☰ in the top bar) Top bar: ☰ · title · Edit · Sources · ⋯ (Export, Help) Full width; hidden while a sheet or the editing bar is up In the Vault footer Bottom sheets

The layout follows the shell's own width (a ResizeObserver), not the screen — a split-screen window, an embedded frame or the docs preview all get the right layout. Touch behaviour is separate and follows the pointer in use (VisualMap interaction="auto"): an iPad in landscape gets the wide layout with tap-to-peek.

Building it

import { AppShell, Vault, VisualMap, ChatTurn, Composer, exportItems } from '@visualli/design-system/components';

<AppShell
  title={doc.title}
  vault={<Vault entries={entries} activeId={id} onOpen={open} onNew={create} onExport={exportAs} user={user} onComfort={showComfort} />}
  actions={[
    { id: 'edit', label: 'Edit', icon: 'pencil', pressed: editing, pinned: true, onClick: toggleEdit },
    { id: 'sources', label: 'Sources', icon: 'paperclip', count: sources.length, pressed: showSources, pinned: true, onClick: toggleSources },
    { id: 'export', label: 'Export', icon: 'download', menu: exportItems(exportCurrent) },
    { id: 'help', label: 'Help', icon: 'help-circle', quiet: true, onClick: openHelp },
  ]}
  map={<VisualMap doc={doc} editing={editing} onEditingChange={setEditing} aside={sourcesAside} … />}
  dock={<>{turns.map((t) => <ChatTurn key={t.id} {...t} />)}<Composer value={draft} onChange={setDraft} onSend={send} /></>}
  onFeedback={() => window.Quackback?.('open')}
>
  {/* dialogs: Comfort & appearance … */}
</AppShell>

What AppShell does for you:

On the page

Add these to the web app's index.html so it behaves like an app on phones:

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#f6f3ee" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0f1220" media="(prefers-color-scheme: dark)">

viewport-fit=cover turns on the safe-area insets AppShell already uses. Don't set maximum-scale or user-scalable=no — people who zoom the page need to.

Rules