lw-ui CSS Framework
lw-ui.css is a CSS-only framework shipped with @linkworld_ai/sdk-browser. It gives partner-app bundles a polished baseline that automatically inherits the active tenant theme (colors, spacing, fonts, radii, shadows) via the --lw-* tokens that AppFrameHost injects.
Available since sdk-browser 0.4.0.
Quick reference
Section titled “Quick reference”What you need → which class. Use this as a cheat sheet.
| Need | Class | Notes |
|---|---|---|
| Page shell | lw-app | Top-level <main> |
| Page header | lw-app-header + lw-app-header__title / __subtitle / __icon | Standard top-bar |
| Vertical group | lw-stack | display: flex; flex-direction: column; gap: … |
| Horizontal group | lw-row | Flex row with gap, wraps |
| Card / panel | lw-card + lw-card-header + lw-card-body | Surfaced container |
| Tabs | lw-tabs + lw-tabs-list + lw-tab | Drive aria-selected="true" yourself |
| Button | lw-btn + lw-btn--primary / --ghost / --danger / --icon | |
| Pill / chip | lw-badge + lw-badge--success / --warning / --danger / --info | Counts, tags, status |
| Input | lw-input (or lw-textarea, lw-select) | Inside a lw-field for label + hint |
| KPI card | lw-kpi + lw-kpi--success / etc. inside lw-kpi-grid | Dashboard metrics |
| Empty state | lw-empty | Centered “nothing here” placeholder |
| Loader | lw-loader | Spinner |
| Search input | lw-search | Search-shaped variant of lw-input |
| Toolbar | lw-toolbar | Header strip with controls |
| Modal | lw-modal | In-bundle overlay (NOT bridge.modal, that’s a sub-iframe) |
| Data table | lw-table | Themed table |
| Muted text | lw-text-muted / lw-text-secondary | Color tokens |
| Mono / numeric | lw-mono / lw-numeric | Font tweaks |
| Truncate text | lw-truncate | One-line ellipsis |
Anti-patterns — don’t do this
Section titled “Anti-patterns — don’t do this”- Don’t define your own theme tokens.
--lw-bg,--lw-surface,--lw-textetc. are injected at runtime by the parent shell. Re-declaring them with hard-coded hex values fights the tenant’s chosen theme. - Don’t roll your own
.my-tab/.my-button/.my-card. Uselw-tab/lw-btn/lw-card. Custom rules fall behind whenever the platform refines its visual language. - Don’t
display: flex !importanton a[hidden]element to “fix” visibility.[hidden] { display: none !important }keeps the HTML attribute behaviour. - Don’t pull theme variables from
localStorageor guess fromprefers-color-scheme. The bridge already injects the active theme on:root; just use the lw-* classes. - Don’t import a CSS framework like Tailwind/Bootstrap/MUI alongside lw-ui. They’ll fight over reset rules and the cascade gets messy. lw-ui is the framework.
The bundle iframe is sandboxed and cross-origin — it cannot import platform React components like <Card> or <Tabs>. lw-ui solves the visual half of that gap with a small, JS-free CSS file. Apps stay vanilla and self-contained; the polish comes from class names.
Install
Section titled “Install”lw-ui.css ships in the sdk-browser dist. The recommended path is to vendor the SDK into your bundle (so it survives the strict CDN CSP) and reference it relatively:
<link rel="stylesheet" href="vendor/sdk-browser/lw-ui.css">If you import sdk-browser via npm, you can also reference it via the package export:
import '@linkworld_ai/sdk-browser/lw-ui.css'Tokens
Section titled “Tokens”Every rule in lw-ui reads from --lw-* CSS custom properties with sensible fallbacks. When the bundle is loaded inside the platform shell, AppFrameHost injects the active theme onto :root. Standalone (e.g. vite dev) the fallbacks produce a usable dark UI.
| Token group | Examples |
|---|---|
| Colors | --lw-bg, --lw-surface, --lw-text, --lw-text-muted, --lw-border, --lw-accent |
| Spacing | --lw-space-xs (4) … --lw-space-2xl (32) |
| Radii | --lw-radius-sm, --lw-radius-md, --lw-radius-lg, --lw-radius-full |
| Fonts | --lw-font-sans, --lw-font-mono |
| Shadows | --lw-shadow-sm, --lw-shadow-md |
Component reference
Section titled “Component reference”All classes start with lw- to avoid colliding with app-specific styles. Specificity is intentionally low (single-class selectors) so app-level overrides win without !important.
Layout
Section titled “Layout”<main class="lw-app"> <header class="lw-app-header"> <div class="lw-app-header__icon">📦</div> <div class="lw-app-header__title-block"> <h1 class="lw-app-header__title">My App</h1> <p class="lw-app-header__subtitle">A short tagline</p> </div> </header>
<div class="lw-stack">…vertical gap stack…</div> <div class="lw-row">…horizontal gap row…</div></main>KPI cards
Section titled “KPI cards”<div class="lw-kpi-grid"> <div class="lw-kpi lw-kpi--success"> <div class="lw-kpi__icon">€</div> <div class="lw-kpi__label">Revenue</div> <div class="lw-kpi__value">€ 12,450</div> <div class="lw-kpi__sub">+18% vs. last month</div> </div> <!-- repeat for warning / info / danger / neutral --></div>Tone modifiers: lw-kpi--success, lw-kpi--warning, lw-kpi--info, lw-kpi--danger. Omit for neutral.
<div class="lw-tabs"> <div class="lw-tabs-list" role="tablist"> <button class="lw-tab" role="tab" aria-selected="true">Active</button> <button class="lw-tab" role="tab" aria-selected="false">Archive</button> </div> <!-- tab content goes here --></div>You handle the click → state → aria-selected flip yourself; the styling reacts to aria-selected="true".
Toolbar + search
Section titled “Toolbar + search”<div class="lw-toolbar"> <div class="lw-search"> <svg class="lw-search__icon">…</svg> <input type="search" placeholder="Search…" /> </div> <select class="lw-select">…</select> <button class="lw-btn lw-btn--primary">+ New</button></div>Buttons
Section titled “Buttons”<button class="lw-btn">Default</button><button class="lw-btn lw-btn--primary">Primary</button><button class="lw-btn lw-btn--ghost">Ghost</button><button class="lw-btn lw-btn--danger">Delete</button><button class="lw-btn lw-btn--icon">⋮</button>Form fields
Section titled “Form fields”<label class="lw-field"> <span class="lw-field__label">Email</span> <input class="lw-input" type="email" /> <span class="lw-field__hint">We'll never share this.</span></label>
<select class="lw-select">…</select><textarea class="lw-textarea">…</textarea><div class="lw-card"> <div class="lw-card-header"> <h3>Heading</h3> <button class="lw-btn lw-btn--ghost">Edit</button> </div> <div class="lw-card-body"> Content </div></div>For tables that span the full card width, use lw-card-body--flush to drop the body padding.
Data table
Section titled “Data table”<table class="lw-table"> <thead> <tr> <th>Name</th> <th>Status</th> <th class="lw-numeric">Amount</th> </tr> </thead> <tbody> <tr class="lw-table__row--clickable"> <td>Acme Corp</td> <td><span class="lw-badge lw-badge--success">Paid</span></td> <td class="lw-numeric">€ 1,240.00</td> </tr> </tbody></table>Badges
Section titled “Badges”<span class="lw-badge">Default</span><span class="lw-badge lw-badge--success">Active</span><span class="lw-badge lw-badge--warning">Pending</span><span class="lw-badge lw-badge--info">Sent</span><span class="lw-badge lw-badge--danger">Cancelled</span>Empty state
Section titled “Empty state”<div class="lw-empty"> <div class="lw-empty__icon">📄</div> <p class="lw-empty__title">No documents yet.</p> <p class="lw-empty__body">Create one with the button above or via chat.</p></div>Loader
Section titled “Loader”<span class="lw-loader" aria-label="Loading"></span>Utilities
Section titled “Utilities”| Class | Effect |
|---|---|
lw-text-muted | Apply --lw-text-muted color |
lw-text-secondary | Apply --lw-text-secondary color |
lw-numeric | Right-align + tabular numerals |
lw-mono | Monospace family |
lw-truncate | One-line ellipsis |
Styling beyond lw-ui
Section titled “Styling beyond lw-ui”Anything not covered by lw-ui is yours to style. Tip: continue to consume the same --lw-* tokens in your own CSS so the result still inherits the active tenant theme:
.my-custom-thing { background: var(--lw-surface, #11141a); border: 1px solid var(--lw-border, #232733);}Reference apps
Section titled “Reference apps”Real bundles to read end-to-end as you onboard:
- examples/office-assistant/ — KPI grid + tabs + toolbar + polished list. Use as a reference for dashboard-shaped apps.
- examples/inbox-manager/ — sub-tabs (
lw-tab) for activity buckets,lw-cardrows withlw-badgepills,lw-btn--ghostretry actions. Reference for activity-feed apps. Settings live ininstall_settings(rendered by the platform’schrome.panel— NO custom settings form needed in the iframe).
Worked example
Section titled “Worked example”The Office Assistant SDK port (examples/office-assistant/) shows KPI grid + tabs + toolbar + polished list, all built on lw-ui classes plus a small app.css for OA-specific bits (detail-view layout, line-items table).