<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Overview · Dashboard · REALM examples</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
<style>
body { margin: 0; }
</style>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toaster.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/unicons@alpha/dist/unicons.html"></import-element>
<import-element from="./components/dashboard-shell.html"></import-element>
<import-element from="./components/overview-page.html"></import-element>
<dashboard-shell page="overview"><overview-page></overview-page></dashboard-shell>
<ui-toaster></ui-toaster>
</body>
</html>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/avatar.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/breadcrumb.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/command.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/dialog.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/dropdown-menu.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/kbd.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/separator.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/sidebar.html"></import-element>
<custom-element name="dashboard-shell">
<script type="module">
import { defaultSettings, pages } from "../data.js";
const props = defineProps({
page: prop.enum(pages.map((item) => item.id)).default("overview"),
});
const settings = defineStore("dashboard-settings", defaultSettings, { persist: "local" });
const shell = useState({
get nav() {
return pages.map((item) => ({ ...item, current: item.id === props.page }));
},
get current() {
return pages.find((item) => item.id === props.page);
},
get initials() {
return settings.name
.split(" ")
.map((part) => part[0])
.join("")
.slice(0, 2);
},
});
const ACCOUNT = {
settings: () => location.assign("settings.html"),
logout: () => dispatch("toast", { title: "Signed out", description: "This demo keeps you signed in." }),
};
function search() {
refs.palette.showModal();
}
function go(event) {
refs.palette.close();
location.assign(event.detail.value);
}
function account(event) {
ACCOUNT[event.detail.value]();
}
function shortcut(event) {
if (event.key.toLowerCase() !== "k" || !(event.metaKey || event.ctrlKey)) return;
event.preventDefault();
search();
}
listen("keydown", shortcut, document);
</script>
<style>
:host {
display: block;
}
.brand {
display: flex;
align-items: center;
gap: calc(var(--spacing) * 2);
padding: calc(var(--spacing) * 2);
overflow: hidden;
font-weight: var(--font-weight-semibold);
white-space: nowrap;
}
.logo {
display: grid;
place-content: center;
flex-shrink: 0;
width: calc(var(--spacing) * 8);
height: calc(var(--spacing) * 8);
border-radius: var(--radius-md);
background: var(--primary);
color: var(--primary-foreground);
}
ui-sidebar-menu-button u-icon {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
.bar {
display: flex;
align-items: center;
gap: calc(var(--spacing) * 3);
height: calc(var(--spacing) * 14);
padding: 0 calc(var(--spacing) * 4);
border-bottom: 1px solid var(--border);
}
.bar ui-separator {
height: calc(var(--spacing) * 4);
align-self: center;
}
.search {
margin-left: auto;
}
.content {
display: grid;
grid-template-columns: minmax(0, 1fr);
align-content: start;
gap: calc(var(--spacing) * 6);
padding: calc(var(--spacing) * 6);
}
h1 {
margin: 0;
font-size: var(--text-2xl);
letter-spacing: -0.02em;
}
.heading p {
margin: calc(var(--spacing) * 1) 0 0;
color: var(--muted-foreground);
font-size: var(--text-sm);
}
@media (max-width: 640px) {
.bar ui-breadcrumb {
display: none;
}
.account-name {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.content {
padding: calc(var(--spacing) * 4);
}
}
</style>
<ui-sidebar-layout persist="realm-dashboard-sidebar">
<ui-sidebar>
<ui-sidebar-header>
<div class="brand"><span class="logo" aria-hidden="true">A</span><span>Acme Store</span></div>
</ui-sidebar-header>
<ui-sidebar-content>
<ui-sidebar-group>
<ui-sidebar-group-label>Store</ui-sidebar-group-label>
<ui-sidebar-menu>
<ui-sidebar-menu-item repeat="#shell.nav" as="item" key="id">
<ui-sidebar-menu-button _href="#item.href" _current="#item.current" _label="#item.label">
<u-icon _name="#item.icon"></u-icon>
<slot name="#item.label"></slot>
</ui-sidebar-menu-button>
</ui-sidebar-menu-item>
</ui-sidebar-menu>
</ui-sidebar-group>
</ui-sidebar-content>
</ui-sidebar>
<ui-sidebar-inset>
<div class="bar">
<ui-sidebar-trigger></ui-sidebar-trigger>
<ui-separator orientation="vertical"></ui-separator>
<ui-breadcrumb>
<ui-breadcrumb-list>
<ui-breadcrumb-item><ui-breadcrumb-link href="./">Acme Store</ui-breadcrumb-link></ui-breadcrumb-item>
<ui-breadcrumb-separator></ui-breadcrumb-separator>
<ui-breadcrumb-item><ui-breadcrumb-page><slot name="#shell.current.label"></slot></ui-breadcrumb-page></ui-breadcrumb-item>
</ui-breadcrumb-list>
</ui-breadcrumb>
<ui-button class="search" variant="outline" size="sm" onclick="search()">
<u-icon name="search"></u-icon> Search… <ui-kbd>⌘K</ui-kbd>
</ui-button>
<ui-dropdown-menu onselect="account(event)">
<ui-dropdown-menu-trigger>
<ui-button variant="ghost" size="sm">
<ui-avatar size="sm" aria-hidden="true"><ui-avatar-fallback><slot name="#shell.initials"></slot></ui-avatar-fallback></ui-avatar>
<span class="account-name"><slot name="#settings.name"></slot></span>
</ui-button>
</ui-dropdown-menu-trigger>
<ui-dropdown-menu-content placement="bottom-end">
<ui-menu-item value="settings">Settings</ui-menu-item>
<ui-menu-item value="logout" variant="destructive">Log out</ui-menu-item>
</ui-dropdown-menu-content>
</ui-dropdown-menu>
</div>
<div class="content">
<div class="heading">
<h1><slot name="#shell.current.label"></slot></h1>
<p><slot name="#shell.current.description"></slot></p>
</div>
<slot></slot>
</div>
</ui-sidebar-inset>
</ui-sidebar-layout>
<ui-dialog ref="palette" label="Go to a page" hide-close>
<ui-command placeholder="Go to a page…" onselect="go(event)">
<optgroup label="Pages">
<option repeat="#shell.nav" as="command" key="id" _value="#command.href" _label="#command.label"></option>
</optgroup>
</ui-command>
</ui-dialog>
</custom-element>
const currency = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
const day = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric" });
export const money = (amount) => currency.format(amount);
export const pages = [
{
id: "overview",
label: "Overview",
href: "./",
icon: "layout-dashboard",
description: "Sales, visitors and recent activity.",
},
{
id: "orders",
label: "Orders",
href: "orders.html",
icon: "shopping-cart",
description: "Every order, with its status and items.",
},
{
id: "customers",
label: "Customers",
href: "customers.html",
icon: "users",
description: "People who bought from the store.",
},
{
id: "products",
label: "Products",
href: "products.html",
icon: "package",
description: "The catalog, prices and stock.",
},
{
id: "settings",
label: "Settings",
href: "settings.html",
icon: "settings",
description: "Your profile and notifications.",
},
];
export const months = [
{ month: "May", desktop: 186, mobile: 80 },
{ month: "Jun", desktop: 305, mobile: 200 },
{ month: "Jul", desktop: 237, mobile: 120 },
{ month: "Aug", desktop: 73, mobile: 190 },
{ month: "Sep", desktop: 209, mobile: 130 },
{ month: "Oct", desktop: 214, mobile: 140 },
];
export const categories = ["Apparel", "Accessories", "Home", "Stationery"];
export const products = [
{ id: 1, name: "Canvas tote", category: "Accessories", price: 24, stock: 42 },
{ id: 2, name: "Linen shirt", category: "Apparel", price: 68, stock: 7 },
{ id: 3, name: "Ceramic mug", category: "Home", price: 18, stock: 120 },
{ id: 4, name: "Dot grid notebook", category: "Stationery", price: 12, stock: 0 },
{ id: 5, name: "Wool beanie", category: "Apparel", price: 32, stock: 25 },
{ id: 6, name: "Desk lamp", category: "Home", price: 89, stock: 4 },
{ id: 7, name: "Leather card case", category: "Accessories", price: 45, stock: 16 },
{ id: 8, name: "Fountain pen", category: "Stationery", price: 54, stock: 9 },
{ id: 9, name: "Hoodie", category: "Apparel", price: 72, stock: 31 },
{ id: 10, name: "Throw blanket", category: "Home", price: 96, stock: 0 },
];
export const stockLevels = [
{ max: 0, label: "Out of stock", variant: "destructive" },
{ max: 9, label: "Low stock", variant: "secondary" },
{ max: Number.POSITIVE_INFINITY, label: "In stock", variant: "outline" },
];
export const stockLevel = (stock) => stockLevels.find((level) => stock <= level.max);
const people = [
["Olivia Martin", "Lisbon"],
["Jackson Lee", "Toronto"],
["Isabella Nguyen", "Melbourne"],
["William Kim", "Seoul"],
["Sofia Davis", "Austin"],
["Noah Fischer", "Berlin"],
["Amara Okafor", "Lagos"],
["Lucas Moreau", "Lyon"],
["Mei Tanaka", "Osaka"],
["Diego Alvarez", "Bogotá"],
["Hannah Berg", "Oslo"],
["Ravi Patel", "Pune"],
];
const statusCycle = [
"Paid",
"Shipped",
"Pending",
"Paid",
"Shipped",
"Refunded",
"Paid",
"Pending",
];
export const statuses = ["Pending", "Paid", "Shipped", "Refunded"];
export const statusVariants = {
Pending: "secondary",
Paid: "default",
Shipped: "outline",
Refunded: "destructive",
};
const today = new Date(2026, 9, 20);
export const orders = Array.from({ length: 36 }, (_, index) => {
const [name] = people[(index * 5) % people.length];
const items = Array.from({ length: (index % 3) + 1 }, (_, line) => {
const product = products[(index * 3 + line * 4) % products.length];
return { product: product.name, quantity: ((index + line) % 2) + 1, price: product.price };
});
return {
id: `ORD-${1036 - index}`,
customer: name,
email: `${name.split(" ")[0].toLowerCase()}@example.com`,
date: day.format(new Date(today.getFullYear(), today.getMonth(), today.getDate() - index)),
status: statusCycle[index % statusCycle.length],
items,
total: items.reduce((sum, item) => sum + item.price * item.quantity, 0),
};
});
export const customers = people.map(([name, city], index) => {
const own = orders.filter((order) => order.customer === name);
return {
id: index + 1,
name,
city,
email: `${name.split(" ")[0].toLowerCase()}@example.com`,
initials: name
.split(" ")
.map((part) => part[0])
.join(""),
joined: day.format(new Date(2025, index % 12, ((index * 7) % 28) + 1)),
orders: own.length,
spent: own.reduce((sum, order) => sum + order.total, 0),
};
});
const earned = orders.filter((order) => order.status !== "Refunded" && order.status !== "Pending");
export const stats = [
{
id: "revenue",
label: "Revenue",
icon: "dollar-sign",
value: money(earned.reduce((sum, order) => sum + order.total, 0)),
change: "+20.1% from last month",
},
{
id: "orders",
label: "Orders",
icon: "shopping-cart",
value: String(orders.length),
change: "+12 since last week",
},
{
id: "customers",
label: "Customers",
icon: "users",
value: String(customers.length),
change: "+3 this month",
},
{
id: "low-stock",
label: "Low or out of stock",
icon: "package",
value: String(
products.filter((product) => stockLevel(product.stock) !== stockLevels.at(-1)).length,
),
change: "Restock soon",
},
];
export const recentSales = earned.slice(0, 5).map((order) => ({
id: order.id,
name: order.customer,
email: order.email,
initials: customers.find((customer) => customer.name === order.customer).initials,
amount: `+${money(order.total)}`,
}));
export const defaultSettings = {
name: "Sam Chen",
email: "sam@example.com",
language: "en",
timezone: "utc",
orderEmails: true,
weeklyReport: true,
productUpdates: false,
pushAlerts: false,
};
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/avatar.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/card.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/chart.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/tabs.html"></import-element>
<custom-element name="overview-page">
<script type="module">
import { months, recentSales, stats } from "../data.js";
const SERIES = { all: "desktop,mobile", desktop: "desktop", mobile: "mobile" };
const overview = useState({
stats,
months,
sales: recentSales,
series: "all",
get y() {
return SERIES[this.series];
},
});
function chartTab(tabs) {
overview.series = tabs.value;
}
</script>
<style>
:host {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: calc(var(--spacing) * 4);
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(calc(var(--spacing) * 52), 1fr));
gap: calc(var(--spacing) * 4);
}
.stat-title {
display: flex;
align-items: center;
justify-content: space-between;
}
.stat-title u-icon {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
color: var(--muted-foreground);
}
.stat-value {
margin: 0;
font-size: var(--text-2xl);
font-weight: var(--font-weight-bold);
}
.stat-change {
margin: calc(var(--spacing) * 1) 0 0;
font-size: var(--text-xs);
color: var(--muted-foreground);
}
.grid {
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
gap: calc(var(--spacing) * 4);
}
.sales {
display: grid;
gap: calc(var(--spacing) * 5);
margin: 0;
padding: 0;
list-style: none;
}
.sale {
display: flex;
align-items: center;
gap: calc(var(--spacing) * 3);
font-size: var(--text-sm);
}
.sale div {
flex: 1;
min-width: 0;
}
.sale p {
margin: 0;
}
.sale small {
color: var(--muted-foreground);
}
.more {
color: inherit;
font-size: var(--text-sm);
font-weight: var(--font-weight-medium);
text-underline-offset: calc(var(--spacing) * 1);
}
@media (max-width: 1000px) {
.grid {
grid-template-columns: minmax(0, 1fr);
}
}
</style>
<section class="stats" aria-label="Key figures">
<repeat-list of="#overview.stats" as="stat" key="id">
<ui-card>
<ui-card-header>
<ui-card-description class="stat-title"><slot name="#stat.label"></slot><u-icon _name="#stat.icon"></u-icon></ui-card-description>
</ui-card-header>
<ui-card-content>
<p class="stat-value"><slot name="#stat.value"></slot></p>
<p class="stat-change"><slot name="#stat.change"></slot></p>
</ui-card-content>
</ui-card>
</repeat-list>
</section>
<div class="grid">
<ui-card>
<ui-card-header>
<ui-card-title level="2">Visitors</ui-card-title>
<ui-card-description>The last 6 months</ui-card-description>
<ui-card-action>
<ui-tabs value="all" onchange="chartTab(this)">
<ui-tabs-list>
<ui-tabs-trigger value="all">All</ui-tabs-trigger>
<ui-tabs-trigger value="desktop">Desktop</ui-tabs-trigger>
<ui-tabs-trigger value="mobile">Mobile</ui-tabs-trigger>
</ui-tabs-list>
</ui-tabs>
</ui-card-action>
</ui-card-header>
<ui-card-content>
<ui-chart x="month" _y="#overview.y" _data="#overview.months" label="Visitors per month"></ui-chart>
</ui-card-content>
</ui-card>
<ui-card>
<ui-card-header>
<ui-card-title level="2">Recent sales</ui-card-title>
<ui-card-description>The latest paid orders.</ui-card-description>
<ui-card-action><a class="more" href="orders.html">All orders</a></ui-card-action>
</ui-card-header>
<ui-card-content>
<ul class="sales">
<li class="sale" repeat="#overview.sales" as="sale" key="id">
<ui-avatar><ui-avatar-fallback><slot name="#sale.initials"></slot></ui-avatar-fallback></ui-avatar>
<div><p><slot name="#sale.name"></slot></p><small><slot name="#sale.email"></slot></small></div>
<strong><slot name="#sale.amount"></slot></strong>
</li>
</ul>
</ui-card-content>
</ui-card>
</div>
</custom-element>