Guidelines
Interface
Guidelines v2.1.0Composition, 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
- Humans:
/guidelines/interface/ - Agents:
https://shellui.com/guidelines/interface.mdorhttps://shellui.com/design.md - Writing: writing.md for voice, punctuation, and banned words
- Tokens: foundations.md and tokens.json
- Brand logos: brand.md only
- Hub: Guidelines
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)
- Preserve supplied facts, package names, URLs, units, qualifiers, and task constraints.
- 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.
- Make the reader's question, strongest supported answer, and material evidence immediately clear.
- Establish Shellui authorship through existing nav/footer chrome, Inter typography, gray canvas, and scarce primary gold.
- Choose a composition specific to this material. Do not clone an unrelated page as a template.
- 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 fromtokens/tokens.jsonintosrc/_data/designTokens.jsandinput.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/sdktiny). Do not cover host chrome. Theme followshtml.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-topand.page-xgutters, footer. Put.page-xon the outer band and keepmax-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>oraria-label - Interactive elements need keyboard handlers (
onKeyDown/onKeyUpwhere click is not enough) <button>for actions;<a>for navigation. Never a<div>with a click handler- Images need
alt(oralt=""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
h1toh6; oneh1per page - Include a skip link to main content on long chrome (shell and marketing header)
scroll-margin-topon 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-nonewithout a focus replacement - Use
:focus-visibleover:focus(avoid a ring on pointer click) - Group focus with
:focus-withinfor 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
autocompleteand a meaningfulname - Use the correct
type(email,tel,url,number) andinputmode - Never block paste (
onPaste+preventDefault) - Labels clickable (
htmlForor 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 (
beforeunloador 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
transformandopacityonly (compositor-friendly) - Never
transition: all; list properties explicitly - Set the correct
transform-origin - SVG: transforms on a
<g>wrapper withtransform-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-colorintentionally overscroll-behavior: containin modals, drawers, and sheets - including shell drawers- During drag: disable text selection,
inerton dragged elements - Drag, swipe, pinch, and path gestures need tap/click and keyboard alternatives unless the gesture is the whole point
autoFocussparingly - 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-hiddenonbody - Flex and grid over JavaScript measurement for layout
- Nested radii: child ≤ parent, concentric (
rounded-mdinsiderounded-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, not100vh, unless you subtract host chrome
Visual language
- Density: developer-facing, not crowded. Match existing
page-top,page-x,max-w-5xl/max-w-3xlprose,rounded-xlcards, and gray borders on feature pages - Light and dark are both first-class. Preview every screen in both. Marketing site:
html.darkpluslocalStorage.theme. Product:@shellui/coretheme color-scheme: darkonhtmlwhen the dark theme is active (fixes scrollbar and native controls)theme-colormeta matches the page background- Native
<select>: explicitbackground-colorandcolor(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-numsfor number columns and comparisonstext-wrap: balanceortext-prettyon headings (prevents widows)- Text containers handle long content:
truncate,line-clamp-*, orbreak-words - Flex children need
min-w-0to 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
widthandheightto avoid layout shift. Lazy load below the fold - Every image needs real
alttext. Usealt=""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 explicitwidthandheight(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/sdktiny 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-motionmedia 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
valueneedonChange(ordefaultValueif uncontrolled) - Date/time rendering: guard against server/client mismatch
suppressHydrationWarningonly 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=noormaximum-scale=1disabling zoomonPastewithpreventDefaulttransition: alloutline-nonewithout 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.*) autoFocuswithout 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
100vhlayouts 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.