Guidelines

Interface

Guidelines v2.1.0

Composition, chrome, layout, accessibility, and visual language for shellui.com, docs, and in-shell apps.

For agents

Read this markdown file - not the HTML. Skills live in the shellui/skills repo (see hub).

content/guidelines/interface.md
https://shellui.com/guidelines/interface.md

Interface rules cover layout, interaction, and composition on shellui.com, docs.shellui.com, and apps hosted in the Shellui iframe. Pair with Writing for copy and Foundations for tokens. Lead with the shipping shell: navigation, authentication, administration, storage, light and dark themes. Keep AI, MCP, and marketplace chrome out of the default UI.

Agents: load this file or /design.md (stable alias). Do not scrape HTML handbooks. The version field in frontmatter is the public "Guidelines v…" label.

How to use this file

Review files against every rule below. Output concise, high-signal findings. Sacrifice grammar for brevity.

Product

Shellui is an open-source web app development platform: a microfrontend shell with shared navigation, authentication, administration, and storage. Audience: developers shipping a product inside that shell.

Precise, calm, direct, technically literate, evidence-led, restrained. Name is Shellui only. Never "The Host". In prose, "the shell" (lowercase) is the running host. Lead with what ships today (iframe host, @shellui/sdk, identity-service, /admin, storage, Files). Keep AI, MCP, marketplace, and app-store chrome quiet unless the page is about that surface.

Priority (when building pages)

  1. Preserve supplied facts, package names, URLs, units, qualifiers, and task constraints.
  2. Preserve the host stack: Eleventy 3, Tailwind CSS v4, Tailwind Plus patterns already in this repo, Alpine for light demos, shadcn-compatible CSS variables. No new framework, CSS library, or parallel token system apart from the page-scoped island exception under Stack.
  3. Make the reader's question, strongest supported answer, and material evidence immediately clear.
  4. Establish Shellui authorship through existing nav/footer chrome, Inter typography, gray canvas, and scarce primary gold.
  5. Choose a composition specific to this material. Do not clone an unrelated page as a template.
  6. Refine responsive behavior and details without weakening the hierarchy.

Ask questions only when proceeding could change product claims, pricing, security, or CTAs. Otherwise omit the unknown, label it, and proceed.

Stack

  • Templates: Nunjucks in src/. Layouts: base, page, feature, post, guidelines, guidelines-foundations.
  • CSS: src/assets/css/input.css → /assets/css/site.css?v=780c7d86. Token values: generated from tokens/tokens.json into src/_data/designTokens.js and input.css.
  • JS: Alpine (src/blocks/), Plus Elements (/assets/js/elements.js?v=120ff5b8), theme toggle (src/assets/js/site.js?v=0dcc97a2).
  • Islands: one page may lazy-load one React bundle for a single interactive piece it cannot express in Alpine. Source in src/islands/, bundled by esbuild with its CSS inlined. Ship a static fallback in Nunjucks and reuse token variables.
  • Images: img/, logos from Brand downloads. Do not stretch or recolor the wordmark.
  • Iframe: site can load in the shell (@shellui/sdk tiny). Do not cover host chrome. Theme follows html.dark.
  • Do not ship a single-file HTML deliverable, a React/Vite rewrite, or a parallel design-system package.

Chrome to reuse: nav.njk (fixed h-16), footer.njk, logo.njk. Main padding: .page-top (below the fixed header) and .page-x (horizontal gutters). Do not add a second header, mega-footer, announcement bar, or extra theme switcher.

Shellui chrome

The product is a microfrontend shell. The host owns shared chrome; the iframe owns the app.

  • Host chrome: sidebar, top bar, or window layout, plus toasts, dialogs, drawers, theme, and language. Do not rebuild those inside the iframe
  • Iframe apps talk to the shell with @shellui/sdk (init, postMessage). Do not reach into host DOM
  • Layout mode is a shell concern (shellui.config.ts). App UI must work in sidebar, top-bar, and window layouts
  • Do not cover the shell header, nav, or toast region. Fixed app bars need an offset for host chrome
  • Authentication, admin (/admin), and Files are shell surfaces. Deep-link them; don't clone their navigation
  • Theme and locale live in the shell. Read them; don't ship a second theme picker in the app unless the app is the settings surface
  • Prefer existing Tailwind tokens from Foundations. Primary accent is honey gold (--color-primary, --color-primary-ink). Do not invent a second brand color
  • Marketing site chrome: fixed header (h-16), .page-top and .page-x gutters, footer. Put .page-x on the outer band and keep max-w-* children unpadded so they align with the header. Sticky in-page UI must clear the header
  • Logos and marks: see Brand. Do not stretch, recolor, or swap in a different wordmark

Accessibility and focus

  • Icon-only buttons need aria-label
  • Form controls need a <label> or aria-label
  • Interactive elements need keyboard handlers (onKeyDown / onKeyUp where click is not enough)
  • <button> for actions; <a> for navigation. Never a <div> with a click handler
  • Images need alt (or alt="" if decorative)
  • Decorative icons need aria-hidden="true"
  • Async updates (toasts, validation) need aria-live="polite". Shell toasts already live in the host - use the SDK, don't mount a second live region that fights the host
  • Semantic HTML (header, nav, main, footer) before ARIA
  • Headings hierarchical h1 to h6; one h1 per page
  • Include a skip link to main content on long chrome (shell and marketing header)
  • scroll-margin-top on heading anchors so the fixed header does not cover the target
  • Meaningful media needs captions, transcripts, or descriptions
  • Media controls need keyboard support; hide decorative media from assistive tech
  • Interactive elements need visible focus: focus-visible:ring-* or equivalent
  • Never outline-none without a focus replacement
  • Use :focus-visible over :focus (avoid a ring on pointer click)
  • Group focus with :focus-within for compound controls
  • Trap focus in dialogs; return it to the trigger on close
  • Hit targets: if the visual control is < 24px, expand the hit area to ≥ 24px; on mobile ≥ 44px
  • Status is not color alone; include a text label
  • Sticky headers, footers, and overlays must not cover the focused element (16 × 4px marketing header; shell top bar in top-bar layout)

Forms and input

  • Inputs need autocomplete and a meaningful name
  • Use the correct type (email, tel, url, number) and inputmode
  • Never block paste (onPaste + preventDefault)
  • Labels clickable (htmlFor or wrapping the control)
  • Disable spellcheck on emails, codes, usernames, tokens (spellCheck={false})
  • Checkboxes and radios: label and control share one hit target (no dead zones)
  • Submit stays enabled until the request starts; spinner during the request; keep the original label
  • Do not pre-disable submit on incomplete forms; submitting surfaces errors
  • Do not block keystrokes on typed fields; validate after input
  • Enter submits when a text input is the only control; in <textarea>, ⌘/Ctrl+Enter submits
  • Errors inline next to fields; focus the first error on submit
  • Placeholders end with … and show an example pattern (your_access_token_here…)
  • autocomplete="off" on non-auth fields to avoid password-manager triggers
  • Warn before navigation with unsaved changes (beforeunload or a router guard)
  • Auth fields belong in the shell when the app is hosted. Don't duplicate sign-in UI in the iframe unless you are replacing the identity backend

Reuse

Prefer shipped Tailwind Plus patterns. Paste into Nunjucks or {% demo %}. Restyle with Shellui tokens. Native <dialog> / el-dialog on this site; shell SDK for toasts inside a hosted app.

Job File Pattern
Product opening src/index.njk Centered hero, dual CTA
Nav src/_includes/nav.njk Flyouts, Alpine, no new menu library
Feature split src/features/index.md, layouts/feature.njk Two-column, one dominant piece
Pricing src/pricing/index.md Two-tier
Article layouts/post.njk Narrow measure
Inner page layouts/page.njk, guidelines.njk Breadcrumb, h1, lede, prose
Code {% highlight %} Shiki
Demo src/blocks/*.njk Real HTML, no iframe

Shellui brand ambient is allowed: a soft primary / amber wash (blur-3xl, from-primary to-amber-200) behind the homepage hero or a quiet CTA. Static is fine. Reject multi-color blobs, logo clouds, gradient fields, testimonial marquees, and fake dashboards. Gradients elsewhere are not allowed except this wash.

Compose

First viewport is the argument, not a masthead plus setup. Start with the reader's job. Executive path (identity, headings, one action) and audit path (commands, config, tables) are both required. Copy follows Writing.

One page-level throughline. One focal relationship per reading moment. Squint: one dominant claim. If every block has equal weight, redesign. Never fill an evidence gap with panels, icons, or effects.

Passes: frame the job → choose composition → apply Foundations tokens → inspect both themes. Deliver the implementation, not a score.

Reject (visual)

  • All-caps tracked eyebrows as decoration
  • Multi-color glows, stripes, glass, ornamental shadows (soft primary/amber hero ambient is allowed)
  • Generic centered hero plus equal-weight card grid
  • Nested cards, or borders used to repair weak hierarchy
  • Arbitrary icon tiles, logo clouds, fake screenshots
  • A second theme picker, second nav, or host chrome rebuilt in an iframe
  • Leading a shipping-product page with AI / MCP / marketplace atmosphere
  • Authoring-process narration in page copy

Voice rejects (banned words, dashes): Writing.

Motion, touch, and layout

  • Honor prefers-reduced-motion (reduced variant or disable). The marketing theme reveal already disables under that media query - match it
  • Animate transform and opacity only (compositor-friendly)
  • Never transition: all; list properties explicitly
  • Set the correct transform-origin
  • SVG: transforms on a <g> wrapper with transform-box: fill-box; transform-origin: center
  • Animations interruptible - respond to input mid-animation
  • Autoplay motion longer than 5 seconds needs pause, stop, or hide controls
  • Muted decorative loops must stop under prefers-reduced-motion
  • touch-action: manipulation (prevents double-tap zoom delay)
  • Set -webkit-tap-highlight-color intentionally
  • overscroll-behavior: contain in modals, drawers, and sheets - including shell drawers
  • During drag: disable text selection, inert on dragged elements
  • Drag, swipe, pinch, and path gestures need tap/click and keyboard alternatives unless the gesture is the whole point
  • autoFocus sparingly - desktop only, single primary input; avoid on mobile
  • Full-bleed layouts need env(safe-area-inset-*) for notches
  • Avoid unwanted scrollbars: fix overflow rather than masking it with overflow-x-hidden on body
  • Flex and grid over JavaScript measurement for layout
  • Nested radii: child ≤ parent, concentric (rounded-md inside rounded-xl)
  • Design empty, sparse, dense, and error states
  • Inline help before tooltips
  • Iframe apps: don't assume viewport height equals the window. The shell chrome eats space. Prefer % / flex inside the iframe, not 100vh, unless you subtract host chrome

Visual language

  • Density: developer-facing, not crowded. Match existing page-top, page-x, max-w-5xl / max-w-3xl prose, rounded-xl cards, and gray borders on feature pages
  • Light and dark are both first-class. Preview every screen in both. Marketing site: html.dark plus localStorage.theme. Product: @shellui/core theme
  • color-scheme: dark on html when the dark theme is active (fixes scrollbar and native controls)
  • theme-color meta matches the page background
  • Native <select>: explicit background-color and color (Windows dark mode)
  • Ellipsis …, not three dots .... Loading states: Loading…, Saving…
  • Punctuation and banned words: Writing guidelines owns them. UI copy follows the same rules
  • Straight quotes in source. Don't convert to curly quotes by hand
  • font-variant-numeric: tabular-nums for number columns and comparisons
  • text-wrap: balance or text-pretty on headings (prevents widows)
  • Text containers handle long content: truncate, line-clamp-*, or break-words
  • Flex children need min-w-0 to allow truncation
  • Handle empty states - don't render broken UI for empty strings or arrays
  • User-generated content: anticipate short, average, and very long inputs
  • Buttons and links need a hover: state. Hover, active, and focus should increase contrast, not flatten it
  • Copy in the UI follows the writing guidelines: active voice, sentence-case page headings, Title Case nav labels, specific button verbs ("Save token", not "Continue")
  • Numerals for counts: "8 apps", not "eight"
  • Error messages include the next step, not only the problem
  • & over "and" only where space is tight (nav, buttons)
  • Brand name Shellui never translates: translate="no" on the wordmark in HTML where auto-translate would garble it

Images and themes

  • Product screenshots ship as a light and dark pair with matching names: *-light.* and *-dark.*
  • Open Graph images and social previews are light only. Platforms show one fixed image
  • Covers and illustrations: prefer a theme-aware SVG. Otherwise use the light image in a bordered card
  • In dark mode, light images sit in a subtle rounded card with a border so they don't glare
  • Ship WebP with a PNG fallback. Set width and height to avoid layout shift. Lazy load below the fold
  • Every image needs real alt text. Use alt="" only for decoration, and not through the shortcode

Use the themedImage shortcode. Pass base paths without the extension; it expects .webp and .png files with the same name. alt, width, and height are required, and the build fails without them. class and loading are optional; loading defaults to lazy.

{% themedImage { light: "/img/docs/files-light", dark: "/img/docs/files-dark", width: 1600, height: 1000, alt: "Files app with a shared folder open" } %}

For SVG, pass the full .svg path instead of a base path. The shortcode outputs a plain <img> with no WebP or PNG variants. The same required fields and .dark toggle apply:

{% themedImage { light: "/img/blog/cover-light.svg", dark: "/img/blog/cover-dark.svg", width: 1200, height: 630, alt: "Shellui cover" } %}

Leave out dark for a light-only image. In dark mode it renders inside a rounded card with a border:

{% themedImage { light: "/img/blog/cover", width: 1200, height: 630, alt: "Shellui cover", loading: "eager" } %}

Performance and state

  • <img> needs explicit width and height (prevents layout shift)
  • Below-fold images: loading="lazy"
  • Above-fold critical images: fetchpriority="high"
  • Large lists (more than 50 items): virtualize or content-visibility: auto
  • No layout reads in render (getBoundingClientRect, offsetHeight, offsetWidth, scrollTop)
  • Batch DOM reads and writes; don't interleave
  • Prefer uncontrolled inputs; controlled inputs must be cheap per keystroke
  • Self-host third-party scripts when practical (@shellui/sdk tiny ships from /assets/js/). Use Subresource Integrity if you must load from a CDN
  • Critical fonts: Inter is self-hosted with preload and font-display: swap. Do not add a second webfont without a reason
  • Prefer compressed video over animated GIF; provide a still alternative
  • Short decorative loops: muted video, prefers-reduced-motion media condition, still fallback
  • URL reflects state - filters, tabs, pagination, expanded panels in query params. Deep-link stateful UI
  • Links are real links (<a>), so Cmd/Ctrl+click and middle-click work
  • Destructive actions need a confirmation modal or an undo window - never immediate
  • Dates and times: Intl.DateTimeFormat, not hardcoded strings
  • Numbers and currency: Intl.NumberFormat
  • Detect language via Accept-Language / navigator.languages, not IP. The shell already owns locale
  • Hydration: inputs with value need onChange (or defaultValue if uncontrolled)
  • Date/time rendering: guard against server/client mismatch
  • suppressHydrationWarning only where it is truly required
  • Eleventy pages are static HTML. Don't hydrate a whole marketing page for a theme toggle - keep Alpine/JS scoped

Anti-patterns (flag these)

  • user-scalable=no or maximum-scale=1 disabling zoom
  • onPaste with preventDefault
  • transition: all
  • outline-none without a focus-visible replacement
  • Click handler navigation without an <a href>
  • <div> or <span> with click handlers that should be <button> or <a>
  • Images without dimensions
  • Large arrays .map() without virtualization
  • Form inputs without labels
  • Icon buttons without aria-label
  • Submit disabled before the user tries (hides validation)
  • Hit targets under 24px (44px on mobile) with no expanded area
  • Hardcoded date/number formats (use Intl.*)
  • autoFocus without a clear reason
  • Animated GIF when compressed video is suitable
  • Gesture-only action without tap/click and keyboard alternative
  • Em dashes or en dashes as punctuation in UI copy (see Writing)
  • Rebuilding shell toasts, dialogs, or nav inside the iframe
  • 100vh layouts that sit under host chrome
  • A second theme picker in a hosted app
  • Shipping-product UI that leads with AI, MCP, or marketplace chrome
  • Missing dark-theme styles (dark: or theme tokens)
  • Focus or headings hidden under the fixed marketing header or shell top bar

Review output

Group by file. Use file:line format (clickable in VS Code). Terse findings.

#### Example (guidelines hub)

src/guidelines/index.njk:12 - icon button missing aria-label
src/guidelines/index.njk:40 - heading target missing scroll-margin-top

#### Example (CSS)

src/assets/css/extras.css:22 - transition: all → list properties

#### Example (pass)

src/_includes/nav.njk - pass

State issue + location. Skip explanation unless the fix is non-obvious. No preamble.