Add a screenshot, product preview, media block, chart, form, or illustration.
`;
const shellPreview = `
Header slotPublic page content
`;
// Markup rendered beside the mount instead of inside its slot -- for host
// components that have no slot but need controls to demonstrate them.
const withBefore =
(before) =>
(title, description, props = {}, slots = {}) => ({
eyebrow: "Recommended",
title,
description,
props,
slots,
before,
});
const standard = (title, description, props = {}, slots = {}) => ({
eyebrow: "Recommended",
title,
description,
props,
slots,
});
const compact = (title, description, props = {}, slots = {}) => ({
eyebrow: "Compact",
title,
description,
props,
slots,
});
const advanced = (title, description, props = {}, slots = {}) => ({
eyebrow: "Advanced",
title,
description,
props,
slots,
});
const TOASTER_DEMO_BUTTONS =
'';
const DATATABLE_COLUMNS_PLAIN =
'[{"key": "name", "label": "Account", "sortable": true}, {"key": "plan", "label": "Plan", "sortable": true}, {"key": "owner", "label": "Owner"}, {"key": "seats", "label": "Seats", "align": "end", "sortable": true}, {"key": "status", "label": "Status", "align": "center"}]';
const DATATABLE_COLUMNS_NARROW =
'[{"key": "name", "label": "Account", "sortable": true, "width": "40%"}, {"key": "seats", "label": "Seats", "align": "end", "sortable": true, "width": "20%"}, {"key": "plan", "label": "Plan", "align": "center", "width": "20%"}, {"key": "status", "label": "Status", "align": "end", "width": "20%"}]';
const DATATABLE_COLUMNS_INBOX =
'[{"key": "owner", "label": "From", "sortable": true}, {"key": "name", "label": "Subject", "sortable": true}, {"key": "plan", "label": "Folder", "align": "center"}, {"key": "statusHtml", "label": "State", "align": "center", "html": true}]';
const DATATABLE_COLUMNS_COMPARE =
'[{"key": "name", "label": "Plan"}, {"key": "plan", "label": "Tier"}, {"key": "owner", "label": "Owner"}, {"key": "seats", "label": "Seats", "align": "end", "type": "number"}, {"key": "status", "label": "Status", "align": "center"}]';
const DATATABLE_ACTIONS =
'[{"id": "refresh", "label": "Refresh", "icon": "icon-[lucide--refresh-cw]", "when": "always"}, {"id": "read", "label": "Mark as read", "icon": "icon-[lucide--mail-open]", "when": "selection"}, {"id": "archive", "label": "Archive", "icon": "icon-[lucide--archive]", "when": "selection"}, {"id": "delete", "label": "Delete", "icon": "icon-[lucide--trash-2]", "when": "selection", "tone": "danger"}]';
const DATATABLE_COLUMNS =
'[{"key": "name", "label": "Account", "sortable": true}, {"key": "plan", "label": "Plan", "sortable": true}, {"key": "owner", "label": "Owner"}, {"key": "seats", "label": "Seats", "align": "end", "sortable": true}, {"key": "statusHtml", "label": "Status", "align": "center", "html": true}]';
const DATATABLE_ROWS =
'[{"id": 1, "name": "Northwind", "plan": "Scale", "owner": "A. Okafor", "seats": 6, "status": "Active", "statusHtml": "Active"}, {"id": 2, "name": "Acme Industrial", "plan": "Team", "owner": "R. Silva", "seats": 13, "status": "Trial", "statusHtml": "Trial"}, {"id": 3, "name": "Globex", "plan": "Enterprise", "owner": "M. Chen", "seats": 20, "status": "Past due", "statusHtml": "Past due"}, {"id": 4, "name": "Initech", "plan": "Starter", "owner": "J. Dubois", "seats": 27, "status": "Active", "statusHtml": "Active"}, {"id": 5, "name": "Umbrella", "plan": "Scale", "owner": "P. Novak", "seats": 34, "status": "Trial", "statusHtml": "Trial"}, {"id": 6, "name": "Stark Labs", "plan": "Team", "owner": "A. Okafor", "seats": 41, "status": "Past due", "statusHtml": "Past due"}, {"id": 7, "name": "Wayne Foods", "plan": "Enterprise", "owner": "R. Silva", "seats": 48, "status": "Active", "statusHtml": "Active"}, {"id": 8, "name": "Soylent", "plan": "Starter", "owner": "M. Chen", "seats": 55, "status": "Trial", "statusHtml": "Trial"}, {"id": 9, "name": "Hooli", "plan": "Scale", "owner": "J. Dubois", "seats": 62, "status": "Past due", "statusHtml": "Past due"}, {"id": 10, "name": "Vehement", "plan": "Team", "owner": "P. Novak", "seats": 69, "status": "Active", "statusHtml": "Active"}, {"id": 11, "name": "Massive Dynamic", "plan": "Enterprise", "owner": "A. Okafor", "seats": 76, "status": "Trial", "statusHtml": "Trial"}, {"id": 12, "name": "Cyberdyne", "plan": "Starter", "owner": "R. Silva", "seats": 83, "status": "Past due", "statusHtml": "Past due"}, {"id": 13, "name": "Tyrell", "plan": "Scale", "owner": "M. Chen", "seats": 90, "status": "Active", "statusHtml": "Active"}, {"id": 14, "name": "Aperture", "plan": "Team", "owner": "J. Dubois", "seats": 97, "status": "Trial", "statusHtml": "Trial"}, {"id": 15, "name": "Black Mesa", "plan": "Enterprise", "owner": "P. Novak", "seats": 104, "status": "Past due", "statusHtml": "Past due"}]';
export const componentProfiles = {
Navbar: {
demos: [
standard(
"Brand, links and actions",
"The everyday shape: a brand, a row of links with the current page marked, and calls to action on the right. Arrow keys move along the menu bar.",
{
brand: { label: "Northwind", description: "Console", href: "/" },
items: [
{ label: "Overview", href: "/", value: "overview" },
{ label: "Projects", href: "/projects", value: "projects" },
{ label: "Reports", href: "/reports", value: "reports" },
],
actions: [
{ label: "Sign in", href: "/login", variant: "link" },
{ label: "Start free", href: "/signup", variant: "primary" },
],
active: "projects",
label: "Primary navigation",
},
),
advanced(
"Dropdown and mega panel",
"An item carrying children opens a panel built from native details and summary, so it is keyboard operable without any extra script. Give a child its own children to get a titled column inside the panel.",
{
brand: { label: "Northwind", icon: "icon-[lucide--box]", href: "/" },
items: [
{ label: "Overview", href: "/", value: "overview" },
{
label: "Products",
value: "products",
columns: 2,
description: "Everything in the platform.",
children: [
{
label: "Platform",
children: [
{
label: "Runtime",
href: "/runtime",
description: "The browser half.",
icon: "icon-[lucide--cpu]",
},
{
label: "Compiler",
href: "/compiler",
description: "WRN to JavaScript.",
icon: "icon-[lucide--hammer]",
},
],
},
{
label: "Tooling",
children: [
{ label: "CLI", href: "/cli", description: "Scaffold and deploy." },
{ label: "Editor", href: "/editor", description: "Language server." },
],
},
],
},
{ label: "Pricing", href: "/pricing", value: "pricing" },
],
actions: [{ label: "Book a demo", href: "/demo", variant: "primary" }],
active: "overview",
},
),
compact(
"Sticky and compact",
"Sticky keeps the bar pinned while the page scrolls. The toggle appears below the tablet breakpoint and the links collapse behind it.",
{
brand: { label: "Acme", href: "/" },
items: [
{ label: "Docs", href: "/docs", value: "docs" },
{ label: "Support", href: "/support", value: "support" },
],
actions: [{ label: "Dashboard", href: "/app", variant: "primary" }],
active: "docs",
sticky: true,
size: "sm",
mobileLabel: "Toggle navigation",
},
),
],
},
LayoutSplitter: {
demos: [
standard(
"Drag or arrow the divider",
"Two panes with a movable divider. Drag it, or focus it and use the arrow keys; Home and End go to the bounds. The size is resolved in the runtime and written onto the container as a custom property, because a pointermove fires far too often to route through a client function.",
{ size: 40, minSize: 20, step: 5, orientation: "horizontal", label: "Resize panels" },
),
advanced(
"Stacked panes",
"Vertical orientation splits top from bottom and switches the arrow keys to up and down. Below the small breakpoint both orientations stack, because two panes side by side stop making sense on a phone.",
{ size: 60, minSize: 25, step: 10, orientation: "vertical", label: "Resize rows" },
),
],
},
CustomScrollbar: {
demos: [
standard(
"Themed scrolling region",
"Scrollbar appearance is CSS rather than script: scrollbar-width and scrollbar-color are the standard properties, with webkit rules for the engines that still need them. The thumb follows the component colour.",
{ axis: "vertical", thickness: 8, maxHeight: "14rem", radius: "999px" },
{
default: `
Line 1. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 2. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 3. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 4. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 5. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 6. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 7. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 8. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 9. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 10. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 11. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
Line 12. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.
`,
},
),
advanced(
"Horizontal, thicker track",
"A horizontal region with a heavier track. thickness is clamped, since it arrives as an attribute and a scrollbar wider than its content helps nobody.",
{ axis: "horizontal", thickness: 14, maxHeight: "8rem", radius: "4px", color: "info" },
{
default: `
`,
},
),
],
},
Scrollspy: {
demos: [
{
eyebrow: "Recommended",
title: "Follows the reader",
description:
"Scroll the page and the marker moves to whichever section is in view. The sections below are real page content, because the runtime observes against the viewport -- a table of contents inside a small scrolling box would have nothing to react to.",
before:
'
Overview
Scroll this page and watch the marker in the list move. The runtime observes these sections against the viewport, so the current link follows whatever you are reading.
Installation
Each link points at one of these ids. Nothing is wired up by hand -- the component only renders the links, and the runtime moves aria-current.
Usage
The active link is marked with aria-current="location", which is what a screen reader announces, and styled through data-active.
API
Clicking a link jumps to its section and marks it immediately, so the control responds even before the scroll settles.
',
props: {
heading: "On this page",
items: [
{ label: "Overview", href: "#overview" },
{ label: "Installation", href: "#installation" },
{ label: "Usage", href: "#usage" },
{ label: "API", href: "#api" },
],
active: "#overview",
},
slots: {},
},
advanced(
"Compact rail",
"On a phone the rail becomes a horizontal strip that scrolls, so a long contents list costs one line rather than a screenful.",
{
heading: "Sections",
size: "sm",
items: [
{ label: "Overview", href: "#overview" },
{ label: "Installation", href: "#installation" },
{ label: "Usage", href: "#usage" },
],
active: "#installation",
},
),
],
},
MegaMenu: {
demos: [
standard(
"Grouped columns",
"One level deep by design: a mega menu shows breadth flat so everything is one click away. Nesting inside the panel would bury content behind hover-within-hover. Use Nav when you want cascading submenus.",
{
label: "Products",
icon: "icon-[lucide--box]",
columns: [
{
heading: "Platform",
items: [
{
label: "Runtime",
href: "/runtime",
description: "The browser half of the framework.",
},
{ label: "Compiler", href: "/compiler", description: "WRN to JavaScript." },
],
},
{
heading: "Tooling",
items: [
{ label: "CLI", href: "/cli", description: "Scaffold, build and deploy." },
{ label: "Editor", href: "/editor", description: "Language server and syntax." },
],
},
],
},
),
advanced(
"With a footer note",
"The panel is anchored, so the runtime clamp pulls it back inside the viewport instead of letting it hang off a wide layout. On a phone it stops floating and joins the flow.",
{
label: "Solutions",
columns: [
{
heading: "By team",
items: [
{ label: "Engineering", href: "/eng", icon: "icon-[lucide--code]" },
{ label: "Design", href: "/design", icon: "icon-[lucide--palette]" },
{ label: "Support", href: "/support", icon: "icon-[lucide--life-buoy]" },
],
},
{
heading: "By size",
items: [
{ label: "Startup", href: "/startup" },
{ label: "Enterprise", href: "/enterprise" },
],
},
],
footer: "Not sure where to start? Talk to us.",
},
),
],
},
Sidebar: {
demos: [
standard(
"Groups and nested levels",
"An entry is a single link, a labelled group, or a branch nested up to three levels. Arrow keys move down the rail.",
{
label: "Workspace",
items: [
{ label: "Dashboard", href: "/", value: "dash", icon: "icon-[lucide--gauge]" },
{
heading: "Projects",
items: [
{ label: "Active", href: "/active", value: "active", badge: "4" },
{
label: "Archive",
value: "archive",
items: [
{ label: "2025", href: "/a/2025", value: "a2025" },
{ label: "2024", href: "/a/2024", value: "a2024" },
],
},
],
},
{
heading: "Account",
items: [
{
label: "Settings",
href: "/settings",
value: "settings",
icon: "icon-[lucide--settings]",
},
],
},
],
active: "active",
},
),
advanced(
"Drawer on small screens",
"Below the tablet breakpoint the rail is replaced by a launcher that opens a Drawer. Composing Drawer rather than reimplementing it means the focus trap and the body scroll lock come from one place.",
{
label: "Operations",
mobileLabel: "Open navigation",
drawerTitle: "Operations",
items: [
{ label: "Queue", href: "/queue", value: "queue", icon: "icon-[lucide--inbox]" },
{
label: "Reports",
href: "/reports",
value: "reports",
icon: "icon-[lucide--chart-no-axes-column]",
},
],
active: "queue",
},
),
],
},
Nav: {
demos: [
standard(
"Horizontal links",
"A flat link bar. The active item is marked with aria-current, and the arrow keys move between items.",
{
items: [
{ label: "Home", href: "/", value: "home", icon: "icon-[lucide--house]" },
{ label: "Inbox", href: "/inbox", value: "inbox", badge: "9" },
{ label: "Reports", href: "/reports", value: "reports" },
{ label: "Archive", href: "/archive", value: "archive", disabled: true },
],
active: "inbox",
},
),
advanced(
"Nested submenus",
"An item carrying its own items array opens a submenu on hover or focus, to a maximum of three levels. On a phone the submenus stack inline instead of floating, because a hover-opened overlay is unreachable on touch.",
{
items: [
{ label: "Home", href: "/", value: "home" },
{
label: "Products",
value: "products",
items: [
{ label: "Overview", href: "/p", value: "p-overview" },
{
label: "Platform",
value: "p-platform",
items: [
{ label: "Runtime", href: "/p/runtime", value: "runtime" },
{ label: "Compiler", href: "/p/compiler", value: "compiler" },
],
},
],
},
],
active: "p-overview",
},
),
standard("Vertical rail", "Vertical orientation switches the arrow keys to up and down.", {
items: [
{ label: "Dashboard", href: "/", value: "dash", icon: "icon-[lucide--gauge]" },
{ label: "Team", href: "/team", value: "team", icon: "icon-[lucide--users]" },
{
label: "Settings",
href: "/settings",
value: "settings",
icon: "icon-[lucide--settings]",
},
],
active: "team",
orientation: "vertical",
collapsible: false,
}),
],
},
Stepper: {
demos: [
standard(
"Horizontal progress",
"Steps before the active one read as complete, the active one is highlighted, and the rest are muted.",
{
steps: [
{ label: "Account", description: "Your details" },
{ label: "Billing", description: "Payment method" },
{ label: "Confirm", description: "Review and submit" },
],
active: 1,
clickable: false,
showPanel: false,
controls: false,
allowSkip: false,
nextDisabled: false,
backLabel: "Back",
nextLabel: "Next",
skipLabel: "Skip",
finishLabel: "Finish",
},
),
standard(
"Vertical with icons",
"Vertical orientation suits a sidebar or a narrow column. Any step may carry an iconify class instead of its number.",
{
steps: [
{ label: "Cloned", icon: "icon-[lucide--git-branch]" },
{ label: "Built", icon: "icon-[lucide--hammer]" },
{ label: "Deployed", icon: "icon-[lucide--rocket]" },
],
active: 2,
orientation: "vertical",
clickable: false,
showPanel: false,
controls: false,
allowSkip: false,
nextDisabled: false,
backLabel: "Back",
nextLabel: "Next",
skipLabel: "Skip",
finishLabel: "Finish",
},
),
advanced(
"Wizard with content and controls",
"showPanel renders each step body and shows only the active one, the same contract Tabs uses. controls adds Back, Skip and Next, which becomes Finish on the last step.",
{
steps: [
{ label: "Account", title: "Your details", content: "Name, email and a password." },
{ label: "Billing", title: "How you pay", content: "Card or invoice." },
{ label: "Confirm", title: "Review", content: "Check everything before submitting." },
],
active: 1,
clickable: false,
showPanel: true,
controls: true,
allowSkip: true,
nextDisabled: false,
backLabel: "Back",
nextLabel: "Next",
skipLabel: "Skip",
finishLabel: "Finish",
},
),
advanced(
"Held until the step is valid",
"The same wizard with nextDisabled set. The component never validates anything itself: the page owns the form, sets this while the step is incomplete, and clears it once the step passes.",
{
steps: [
{ label: "Account", title: "Your details", content: "This step is not complete yet." },
{ label: "Billing", title: "How you pay", content: "Card or invoice." },
],
active: 0,
clickable: false,
showPanel: true,
controls: true,
allowSkip: false,
nextDisabled: true,
backLabel: "Back",
nextLabel: "Next",
skipLabel: "Skip",
finishLabel: "Finish",
},
),
advanced(
"Clickable steps",
"With clickable the steps emit a change output and take arrow-key roving focus. Without it the stepper is read-only and stays out of the tab order.",
{
steps: [{ label: "One" }, { label: "Two" }, { label: "Three" }],
active: 0,
clickable: true,
showPanel: false,
controls: false,
allowSkip: false,
nextDisabled: false,
backLabel: "Back",
nextLabel: "Next",
skipLabel: "Skip",
finishLabel: "Finish",
},
),
],
},
Pagination: {
demos: [
standard(
"Compact arrows",
"The default: previous and next with a page counter, and a summary of the range in view.",
{ page: 2, pageSize: 10, total: 137, variant: "compact" },
),
standard(
"Numbered pages",
"Page numbers windowed around the current page with ellipsis gaps, so a large set never renders hundreds of buttons.",
{ page: 5, pageSize: 10, total: 200, variant: "numbered", siblingCount: 1 },
),
advanced(
"Wider window",
"siblingCount widens how many pages sit either side of the current one.",
{ page: 8, pageSize: 25, total: 900, variant: "numbered", siblingCount: 2 },
),
],
},
DataTable: {
demos: [
standard(
"Sortable, searchable and paged",
"Click a header to sort, type to filter, and page through the results. Every derivation is a shared function, so the first page is server rendered and stays live after hydration.",
{
columns: DATATABLE_COLUMNS_PLAIN,
rows: DATATABLE_ROWS,
caption: "Accounts",
description: "15 records across four plans.",
pageSize: 5,
pageSizes: [5, 10, 25],
selectable: true,
actions: [],
},
),
advanced(
"Bulk actions on selection",
"An inbox pattern: Refresh is always available, while Mark as read, Archive and Delete appear only once something is ticked. Each handler receives the selected records, and an action output fires for every click.",
{
columns: DATATABLE_COLUMNS_INBOX,
rows: DATATABLE_ROWS,
actions: DATATABLE_ACTIONS,
selectable: true,
caption: "Inbox",
description: "Tick a row to reveal the selection-only actions.",
pageSize: 6,
pageSizes: [6, 12, 25],
searchPlaceholder: "Search mail",
},
),
standard(
"Numbered pagination",
"Page numbers instead of arrows, windowed around the current page so a large table never renders hundreds of buttons.",
{
columns: DATATABLE_COLUMNS_PLAIN,
rows: DATATABLE_ROWS,
actions: [],
paginationStyle: "numbered",
pageSize: 4,
pageSizes: [4, 8, 15],
caption: "Accounts",
description: "15 records, four per page.",
},
),
advanced(
"Full gridlines and custom cell markup",
'gridlines="grid" adds column rules as well as row rules. The Status column is rendered from markup rather than text, so a cell can hold a badge, a link or any other component output.',
{
columns: DATATABLE_COLUMNS,
rows: DATATABLE_ROWS,
actions: [],
gridlines: "grid",
striped: false,
pageSize: 5,
pageSizes: [5, 10, 25],
caption: "Accounts",
description: "Status is supplied as markup through a html column.",
},
),
compact(
"Fixed widths and alignment",
"Columns can set their own width and alignment: text left, counts right, short labels centred. Widths are plain CSS, so percentages, rem and ch all work.",
{
columns: DATATABLE_COLUMNS_NARROW,
rows: DATATABLE_ROWS,
actions: [],
searchable: false,
pageSize: 5,
pageSizes: [5, 10, 25],
density: "comfortable",
caption: "Column sizing",
},
),
advanced(
"Comparison layout",
'layout="comparison" transposes the table: fields run down the left and each record gets its own column. The label column stays put while the rest scrolls sideways.',
{
columns: DATATABLE_COLUMNS_COMPARE,
rows: DATATABLE_ROWS,
actions: [],
layout: "comparison",
searchable: false,
pageSize: 3,
pageSizes: [3, 5, 8],
caption: "Feature",
},
),
standard(
"Sticky first column",
"With many columns the table scrolls sideways; stickyFirstColumn keeps the record name in view so a row never loses its label.",
{
columns: DATATABLE_COLUMNS_PLAIN,
rows: DATATABLE_ROWS,
actions: [],
stickyFirstColumn: true,
pageSize: 5,
pageSizes: [5, 10, 25],
caption: "Accounts",
description: "Scroll the table horizontally on a narrow screen.",
},
),
compact("Compact density, no pagination", "Every row at once, tighter rows, no footer.", {
columns: DATATABLE_COLUMNS_PLAIN,
rows: DATATABLE_ROWS,
pageSizes: [5, 10, 25],
actions: [],
paginated: false,
density: "compact",
searchable: false,
striped: false,
caption: "All accounts",
}),
advanced("Empty state", "What a table shows before any records arrive.", {
columns: DATATABLE_COLUMNS_PLAIN,
rows: "[]",
pageSizes: [5, 10, 25],
actions: [],
caption: "Accounts",
emptyLabel: "No accounts match this filter yet",
}),
],
playground: {
columns: DATATABLE_COLUMNS,
rows: DATATABLE_ROWS,
caption: "Accounts",
pageSize: 5,
pageSizes: [5, 10, 25],
actions: DATATABLE_ACTIONS,
selectable: true,
},
options: {
density: ["compact", "default", "comfortable"],
paginationStyle: ["compact", "numbered"],
gridlines: ["rows", "grid"],
color: ["primary", "secondary", "success", "danger", "info"],
},
},
// A Toaster is a HOST, not a widget. The generic profile rendered five empty
// boxes -- and because every mounted host answers the same window event, one
// toast() call produced five toasts. One host plus buttons that actually
// raise notifications is the only demo that shows anything.
Toaster: {
demos: [
withBefore(TOASTER_DEMO_BUTTONS)(
"Raise notifications from anywhere",
"Mount one host, then call toast() from any client function. Each tone brings its own icon and colour, and hovering the stack holds a toast open while you read it. Actions here show label-only for brevity; attach onClick/onAction handlers from a functions{} block.",
{ position: "bottom-right", duration: 4500, max: 4 },
),
],
playground: { position: "bottom-right", duration: 4500, max: 4 },
options: {
position: [
"top-left",
"top-center",
"top-right",
"bottom-left",
"bottom-center",
"bottom-right",
],
size: ["sm", "default", "lg"],
},
},
PublicPageShell: {
demos: [
standard(
"Complete public page shell",
"Use the shell around a complete marketing or public-service page.",
{ maxWidth: "xl", background: "default" },
{ default: shellPreview },
),
compact(
"Constrained documentation shell",
"Keep long-form documentation readable with a compact content width.",
{ maxWidth: "compact", headerOffset: "sm", background: "soft" },
{ default: shellPreview },
),
advanced(
"Full-bleed landing page",
"Allow full-width sections while keeping overflow and minimum-height behavior consistent.",
{ maxWidth: "full", fullWidth: true, minHeight: "screen", overflow: "clip" },
{ default: shellPreview },
),
],
options: {
maxWidth: ["compact", "lg", "xl", "wide", "2xl", "full"],
headerOffset: ["none", "sm", "md", "lg"],
background: ["default", "soft", "raised", "transparent"],
overflow: ["visible", "hidden", "clip", "auto"],
minHeight: ["none", "screen", "full"],
},
},
AnnouncementBar: {
demos: [
standard(
"Soft product notice",
"Use a calm, dismissible notice inside a constrained content area.",
{
badge: "Public notice",
badgeIcon: "icon-[lucide--sparkles]",
message: "All composite components are now available",
description: "Review each component and its recommended use cases.",
icon: "icon-[lucide--megaphone]",
actionLabel: "View components",
actionHref: "#components",
actionIcon: "icon-[lucide--arrow-down]",
dismissible: true,
width: "default",
variant: "soft",
},
),
compact(
"Wide solid announcement",
"Use stronger contrast for important platform or service announcements.",
{
badge: "Release",
message: "WRNexusJS UI updates are ready",
description: "Explore the redesigned responsive components.",
icon: "icon-[lucide--rocket]",
actionLabel: "Read changes",
actionHref: "#release",
dismissible: true,
width: "wide",
compact: true,
variant: "solid",
},
),
advanced(
"Full-width critical alert",
"Use edge-to-edge width for time-sensitive information that applies to the complete page.",
{
badge: "Service alert",
message: "Scheduled maintenance tonight",
description: "Some services may be temporarily unavailable from 23:00 to 23:30.",
icon: "icon-[lucide--triangle-alert]",
actionLabel: "View status",
actionHref: "#status",
dismissible: true,
width: "full",
color: "warning",
variant: "solid",
},
),
],
playground: { width: "wide", dismissible: true, variant: "solid" },
options: {
width: ["default", "wide", "full"],
variant: ["default", "soft", "outline", "solid", "minimal"],
size: ["compact", "default", "spacious"],
color: ["primary", "secondary", "info", "success", "warning", "danger"],
role: ["status", "alert"],
live: ["off", "polite", "assertive"],
},
},
Breadcrumb: {
demos: [
standard("Minimal page trail", "Use a restrained breadcrumb above standard page content.", {
items: breadcrumbs,
size: "small",
variant: "minimal",
}),
compact(
"Soft navigation trail",
"Add a soft surface when the breadcrumb needs more separation from surrounding content.",
{ items: breadcrumbs, size: "medium", variant: "soft", showHome: true },
),
advanced(
"Large directory breadcrumb",
"Use a larger breadcrumb in directories, dashboards, and touch-first interfaces.",
{
items: breadcrumbs,
size: "large",
variant: "outline",
showHome: true,
separator: "slash",
},
),
],
options: {
size: ["small", "medium", "large"],
separator: ["chevron", "slash", "dot", "arrow"],
variant: ["minimal", "soft", "outline", "contrast"],
},
},
PageHeader: {
demos: [
standard(
"Page header with actions",
"Introduce a service or documentation page with breadcrumbs and clear actions.",
{
eyebrow: "Component showcase",
title: "Page headers with clear hierarchy",
description:
"Use PageHeader for services, directories, legal pages, guides, dashboards, and public information.",
icon: "icon-[lucide--panel-top]",
showBreadcrumbs: true,
breadcrumbs,
primaryLabel: "Primary action",
primaryHref: "#primary",
primaryIcon: "icon-[lucide--arrow-down]",
secondaryLabel: "Secondary action",
secondaryHref: "#secondary",
secondaryIcon: "icon-[lucide--house]",
maxWidth: "full",
},
),
compact(
"Compact service header",
"Use reduced spacing when a page already sits inside an application shell.",
{
eyebrow: "Citizen service",
title: "Track a complaint",
description: "Check the latest status using your complaint reference.",
icon: "icon-[lucide--scan-search]",
compact: true,
primaryLabel: "Track status",
primaryHref: "#track",
variant: "soft",
},
),
advanced(
"Centered campaign header",
"Use a centered header for campaigns, guides, and high-level public information.",
{
eyebrow: "Safety guide",
title: "Prepare your family for emergencies",
description:
"Practical guidance for creating a plan, preparing supplies, and staying informed.",
centered: true,
align: "center",
variant: "minimal",
maxWidth: "wide",
},
),
],
playground: { showBreadcrumbs: true, breadcrumbs, maxWidth: "full" },
options: {
align: ["left", "center", "right"],
size: ["small", "default", "large"],
maxWidth: ["compact", "lg", "xl", "wide", "2xl", "full"],
variant: ["default", "minimal", "soft", "raised", "tinted", "solid"],
color: ["primary", "secondary", "info", "success", "warning", "danger"],
},
},
Hero: {
demos: [
standard(
"Split hero with visual",
"Lead a landing page with clear content and a flexible visual slot.",
{
eyebrow: "Reusable public UI",
eyebrowIcon: "icon-[lucide--sparkles]",
title: "Build polished pages",
highlight: "with consistent hero layouts",
description:
"Create strong first impressions for public portals, product pages, and service overviews.",
layout: "split",
visualPosition: "right",
visualStyle: "card",
primaryLabel: "Explore components",
primaryHref: "#components",
primaryIcon: "icon-[lucide--arrow-right]",
secondaryLabel: "Read documentation",
secondaryHref: "#docs",
secondaryIcon: "icon-[lucide--book-open]",
maxWidth: "wide",
variant: "gradient",
},
{ visual: visualPanel },
),
compact(
"Centered campaign hero",
"Use a stacked, centered layout for announcements, campaigns, and simple conversion pages.",
{
eyebrow: "Public campaign",
title: "Safer communities start with trusted information",
description: "Find verified guidance, public notices, and citizen services in one place.",
align: "center",
layout: "stacked",
size: "default",
variant: "soft",
primaryLabel: "Browse services",
primaryHref: "#services",
},
),
advanced(
"Hero with badges and trust",
"Add product proof, audience badges, and supporting trust information.",
{
eyebrow: "Developer platform",
title: "Ship reliable interfaces faster",
highlight: "without duplicating design work",
description:
"Reusable components, server rendering, themes, accessibility, and responsive behavior.",
badges: ["SSR first", "Theme aware", "Accessible"],
trustItems: ["108 UI components", "Light and dark themes", "Mobile ready"],
layout: "split",
visualPosition: "left",
visualStyle: "soft",
variant: "raised",
},
{ visual: visualPanel },
),
],
playground: { layout: "split", visualPosition: "right", maxWidth: "wide" },
options: {
align: ["left", "center", "right"],
size: ["compact", "default", "lg", "xl"],
layout: ["split", "stacked", "content"],
visualPosition: ["left", "right"],
visualStyle: ["plain", "card", "soft"],
variant: ["default", "soft", "raised", "outline", "gradient", "solid", "minimal"],
maxWidth: ["compact", "lg", "xl", "wide", "2xl", "full"],
},
},
HeroActions: {
demos: [
standard(
"Primary and secondary actions",
"Group the most important hero actions with consistent spacing and hierarchy.",
{ actions: heroActions, align: "left", orientation: "horizontal" },
),
compact(
"Centered campaign actions",
"Center actions beneath a stacked hero or CTA message.",
{ actions: heroActions, align: "center", orientation: "horizontal", size: "sm" },
),
advanced(
"Vertical mobile-first actions",
"Use a vertical group for narrow panels, onboarding, or three-action flows.",
{
actions: [
...heroActions,
{
label: "Contact support",
href: "#support",
icon: "icon-[lucide--life-buoy]",
variant: "ghost",
},
],
orientation: "vertical",
fullWidthMobile: true,
size: "lg",
},
),
],
playground: { actions: heroActions },
options: {
align: ["left", "center", "right"],
orientation: ["horizontal", "vertical"],
size: ["sm", "default", "lg"],
},
},
SplitHero: {
demos: [
standard(
"Balanced content and visual",
"Explain a product, department, or service with a balanced two-column introduction.",
{
eyebrow: "Balanced layout",
title: "Combine detailed content",
highlight: "with a strong visual panel",
description:
"Use SplitHero for product explanations, department introductions, campaigns, and feature storytelling.",
primaryLabel: "View public services",
primaryHref: "#services",
primaryIcon: "icon-[lucide--arrow-right]",
secondaryLabel: "Contact department",
secondaryHref: "#contact",
secondaryIcon: "icon-[lucide--phone]",
visualPosition: "right",
ratio: "balanced",
maxWidth: "wide",
},
{ visual: visualPanel },
),
compact(
"Visual-first introduction",
"Reverse the layout when the screenshot, map, or media needs first visual priority.",
{
eyebrow: "Visual first",
title: "Show the interface before the explanation",
description: "Place the visual on the left and use a compact content column.",
visualPosition: "left",
reverse: true,
ratio: "visual",
variant: "soft",
},
{ visual: visualPanel },
),
advanced(
"Structured service overview",
"Use structured visual items for capabilities, facts, or proof points.",
{
eyebrow: "Department overview",
title: "One service, clearly explained",
description: "Pair narrative content with structured supporting information.",
visualTitle: "Service highlights",
visualDescription: "A consistent, responsive supporting panel.",
visualItems: [
{ label: "Online access", value: "24/7", icon: "icon-[lucide--globe-2]" },
{ label: "Response tracking", value: "Live", icon: "icon-[lucide--activity]" },
{ label: "Languages", value: "3", icon: "icon-[lucide--languages]" },
],
ratio: "content",
variant: "gradient",
},
),
],
options: {
visualPosition: ["left", "right"],
ratio: ["balanced", "content", "visual"],
align: ["left", "center", "right"],
variant: ["default", "soft", "raised", "gradient", "solid", "minimal"],
maxWidth: ["compact", "lg", "xl", "wide", "2xl", "full"],
},
},
Section: {
demos: [
standard(
"Standard content section",
"Create predictable vertical rhythm around a page section.",
{ id: "standard-section", spacing: "lg", maxWidth: "xl" },
{ default: '
Structured editorial content can use a wider two-column layout on large screens.
Responsive behavior returns content to one column on smaller screens.
',
},
),
],
options: {
columns: ["1", "2", "3"],
gap: ["sm", "md", "lg", "xl"],
maxWidth: ["compact", "lg", "xl", "wide", "2xl", "full"],
size: ["compact", "default", "spacious"],
},
},
Divider: {
demos: [
standard(
"Horizontal section divider",
"Separate related content without introducing another container.",
{ orientation: "horizontal", label: "Related information" },
),
compact("Minimal unlabeled divider", "Use a quiet rule between compact content groups.", {
orientation: "horizontal",
label: "",
size: "sm",
}),
advanced(
"Vertical toolbar divider",
"Separate groups of inline actions in toolbars and navigation.",
{ orientation: "vertical", label: "Actions", size: "lg", color: "info" },
),
],
options: { orientation: ["horizontal", "vertical"], size: ["sm", "default", "lg"] },
},
};
const genericEventDescriptions = {
action: "Fires when the component's primary or item action is activated.",
blur: "Fires when focus leaves the interactive control.",
cancel: "Fires when the user cancels or dismisses the current workflow.",
change: "Fires when the component's committed value or selection changes.",
clear: "Fires after the current value or selection is cleared.",
click: "Fires when the interactive surface is activated.",
close: "Fires after the component closes.",
confirm: "Fires when the user confirms the current action.",
dismiss: "Fires after the announcement or feedback surface is dismissed.",
error: "Fires when the component cannot complete an operation.",
focus: "Fires when the interactive control receives focus.",
input: "Fires as the editable value changes.",
load: "Fires after remote or deferred content loads successfully.",
open: "Fires after the component opens.",
paste: "Fires after clipboard content is processed.",
search: "Fires when the current search query changes.",
select: "Fires when an item, option, card, or result is selected.",
strength: "Fires when the calculated password-strength state changes.",
toggle: "Fires when the component changes between open and closed or on and off states.",
};
export function profileFor(name) {
return componentProfiles[name] ?? null;
}
export function eventDescriptionFor(_componentName, eventName) {
return (
genericEventDescriptions[eventName] ?? `Fires when the component emits the ${eventName} event.`
);
}