Component playground
Live, hand-testable examples of every component in @f-ewald/components.
address-autocomplete
Form-associated address input with a suggestion dropdown — backed here by a local
array (try "St", "Ave", or "London"), or point endpoint at a geocoding API instead.
import "@f-ewald/components/address-autocomplete.js";
<address-autocomplete placeholder="Start typing an address…"></address-autocomplete>
<script type="module">
// Local mode: filters client-side, no network request.
document.querySelector("address-autocomplete").suggestions = [
{ address: "1 Infinite Loop, Cupertino, CA", lat: 37.3318, lng: -122.0312 },
{ address: "10 Downing Street, London", lat: 51.5034, lng: -0.1276 },
];
// API mode: omit `suggestions` and set `endpoint` + `access-token` instead.
</script>
animate-confetti
Fullscreen confetti burst overlay.
import "@f-ewald/components/animate-confetti.js";
<animate-confetti duration="6000"></animate-confetti>
autocomplete-input
Generic form-associated autocomplete for any {key, value} option list —
backed here by a local array (try "Type" or "Java"), or point endpoint at
an API that returns [{key, value}] for a query string instead.
import "@f-ewald/components/autocomplete-input.js";
<form>
<autocomplete-input name="language" placeholder="Start typing a language…"></autocomplete-input>
</form>
<script type="module">
// Local mode: filters client-side, no network request.
document.querySelector("autocomplete-input").options = [
{ key: "ts", value: "TypeScript" },
{ key: "py", value: "Python" },
];
// API mode: omit `options` and set `endpoint` instead — it's queried as
// `${endpoint}?${queryParam}=` and must respond with [{key, value}].
</script>
calendar-month
One month as a top-to-bottom day list. Weekends are highlighted, today is marked,
and overlapping declarative calendar-entry children stack into aligned
lanes rather than hiding one another. Named title/detail slots fill one line per
visible day, with overflow details hidden automatically.
import "@f-ewald/components/calendar-month.js";
<calendar-month year="2026" month="7">
<calendar-entry start="2026-07-10" end="2026-07-18" label="Vacation" color="success">
<span slot="title">Vacation</span>
<span slot="detail">Out of office</span>
<span slot="detail">Road trip along the California coast with several scenic stops</span>
<span slot="footer">Return July 19 at 6 PM</span>
</calendar-entry>
<calendar-entry start="2026-07-15" end="2026-07-20" label="Conference" color="warning" href="#conf">
<span slot="detail">Talks and workshops</span>
<span slot="footer">Closing keynote · July 20</span>
</calendar-entry>
<calendar-entry start="2026-07-23" end="2026-07-23" label="Appointment" color="info">
<span slot="title">Dentist</span>
<span slot="detail">Hidden because this entry only spans one day</span>
<span slot="footer">Also hidden for a one-day entry</span>
</calendar-entry>
</calendar-month>
calendar-year
A full year of calendar-month blocks generated from declarative
calendar-entry children — including entries that cross a month
boundary or extend past the edges of the displayed year.
import "@f-ewald/components/calendar-year.js";
<calendar-year year="2026">
<calendar-entry start="2026-01-28" end="2026-02-03" label="Offsite" color="primary" href="#offsite">
<span slot="detail">New York</span>
<span slot="detail">Team workshops</span>
<span slot="footer">Closing dinner Friday</span>
</calendar-entry>
<calendar-entry start="2026-03-05" end="2026-03-18" label="Product launch" color="success" href="#launch">
<span slot="detail">Coordinate the release across engineering, design, support, and marketing.</span>
<span slot="detail">Monitor adoption and production health throughout the rollout.</span>
<span slot="footer">Public launch · March 18 at 9 AM</span>
</calendar-entry>
<calendar-entry start="2026-07-10" end="2026-07-18" label="Vacation" color="success"></calendar-entry>
</calendar-year>
chat-message
One conversation entry — tool calls and thinking traces are dimmed, collapsible variants of the same component.
directory: . filename: notes.md content: | Autumn leaves falling...
import "@f-ewald/components/chat-message.js";
<chat-message role="user" author="Freddy" timestamp="2026-07-19T12:00:00Z">
Write notes.md containing a haiku.
</chat-message>
<chat-message role="agent" variant="tool" collapsible collapsed summary='file_write · {"filename": "notes.md"}'>
directory: .
filename: notes.md
</chat-message>
confirm-dialog
Overlay confirmation dialog with Cancel/Confirm actions.
confirm: 0 · cancel: 0
import "@f-ewald/components/confirm-dialog.js";
<confirm-dialog open confirm-label="Delete" cancel-label="Cancel">
Are you sure you want to delete this item?
</confirm-dialog>
data-table
Generic table shell: a header from columns, one row per rows
entry, cell content from renderCell. Optional rowHref makes
whole rows clickable.
import "@f-ewald/components/data-table.js";
const table = document.querySelector("data-table");
table.columns = [
{ key: "title", label: "Title" },
{ key: "state", label: "State" },
];
table.rows = [
{ id: "tsk_1", title: "Write onboarding docs", state: "Backlog" },
{ id: "tsk_2", title: "Fix the login bug", state: "Done" },
];
table.rowHref = (row) => `#/tasks/${row.id}`;
distance-value
Inline distance display, switching units at sensible thresholds.
import "@f-ewald/components/distance-value.js";
<distance-value miles="5"></distance-value>
distribution-chart
KDE distribution curve for a named metric with value markers.
import "@f-ewald/components/distribution-chart.js";
<distribution-chart metric="sqft"></distribution-chart>
<script>
document.querySelector("distribution-chart").values = [{ label: "", value: 1450 }];
</script>
editable-text
Click-to-edit text: a display span that turns into an input/textarea on click.
Single-line (title)
Multiline (description)
import "@f-ewald/components/editable-text.js";
<editable-text value="Write the quarterly report" label="Title"></editable-text>
<editable-text multiline placeholder="Add a description…" label="Description"></editable-text>
form-select
Styled dropdown select: a trigger button opening a listbox popover, firing
change with { value }. Enable
searchable for case-insensitive infix filtering while still
requiring an explicit option selection.
Searchable (try gress or VIEW)
import "@f-ewald/components/form-select.js";
import {
iconArrowPath,
iconCheckCircle,
iconEye,
iconListBullet,
} from "@f-ewald/components/icons.js";
const select = document.querySelector("form-select");
select.options = [
{ value: "backlog", label: "Backlog", icon: iconListBullet(14), iconSize: 14 },
{ value: "open", label: "Open" },
{ value: "in_progress", label: "In progress", icon: iconArrowPath(16), iconSize: 16 },
{ value: "review", label: "Needs review", icon: iconEye(18), iconSize: 18 },
{ value: "done", label: "Done", icon: iconCheckCircle(16), iconSize: 16 },
];
select.value = "open";
select.searchable = true;
select.addEventListener("change", (e) => console.log(e.detail.value));
frame-box
A titled frame around a slot — a gray border with a small uppercase, muted label overlapping the top edge. Generic; the label text is up to the consumer.
Framed content goes here.
import "@f-ewald/components/frame-box.js";
<frame-box label="Debug">
Framed content goes here.
</frame-box>
kbd-hint
Platform-aware keyboard shortcut hints rendered as compact keycaps. Use
Mod for Command on macOS and Control elsewhere, or override
platform for deterministic output.
import "@f-ewald/components/kbd-hint.js";
<kbd-hint keys="Mod+K"></kbd-hint>
<kbd-hint keys="Mod+Shift+Enter" platform="mac"></kbd-hint>
live-timer
Per-second ticking count-up timer.
import "@f-ewald/components/live-timer.js";
<live-timer since="2026-07-19T12:00:00Z" prefix="Sleeping for "></live-timer>
<live-timer since="2026-07-19T12:00:00Z" format="compact" prefix="running for "></live-timer>
map-circle
Plain circular marker — a light-to-dark gradient fill with a white outer ring, no point/tail. Use it as an individual DOM marker with optional badge content, or rasterize a small instance once for a dense point layer. No mapping-library dependency.
Configurable ring-width and size
import "@f-ewald/components/map-circle.js";
<map-circle color="#6b7280"></map-circle>
<map-circle color="#0099D8" size="14" ring-width="3"></map-circle>
<map-circle color="#1a73e8" size="24" ring-width="5" highlighted>1</map-circle>
map-pin
Circular "Apple Maps"-style pin — a light-to-dark gradient fill with a slight point at the bottom. No mapping-library dependency; the consumer positions it (e.g. via a mapboxgl.Marker).
import "@f-ewald/components/map-pin.js";
<map-pin color="#1a73e8" size="30">3</map-pin>
<map-pin color="#22c55e" size="26" highlighted>🏠</map-pin>
percent-bar-chart
Horizontal bar chart of labeled percentage rows.
import "@f-ewald/components/percent-bar-chart.js";
document.querySelector("percent-bar-chart").groups = [
{ key: "a", label: "White", pct: 45.2, color: "#4f46e5" },
{ key: "b", label: "Asian", pct: 28.1, color: "#0d9488" },
];
photo-gallery
Responsive carousel with accessible controls, autoplay, keyboard navigation, swipe gestures, and declarative image metadata.
Showing image 1 of 3
import "@f-ewald/components/photo-gallery.js";
<photo-gallery delay="5000" show-counter show-indicators>
<gallery-item
src="/photos/coast.jpg"
alt="Rocky California coastline"
caption="California coast"
>
<gallery-item-variant
media="(max-width: 640px)"
srcset="/photos/coast-portrait.jpg"
></gallery-item-variant>
</gallery-item>
<gallery-item src="/photos/bridge.jpg" alt="Golden Gate Bridge"></gallery-item>
</photo-gallery>
popover-panel
Anchored floating popover — like slide-panel, but positioned relative to
its trigger instead of docked to the screen edge. Closes on outside click or Escape.
import "@f-ewald/components/popover-panel.js";
<div style="position: relative; display: inline-block;">
<button>New task</button>
<popover-panel open heading="New task">
<a slot="actions" href="#/tasks/new">Full page ↗</a>
Popover body content goes here.
</popover-panel>
</div>
<!-- Screen-centered modal variant -->
<popover-panel centered open heading="New task">
Popover body content goes here.
</popover-panel>
price-history-chart
D3-powered SVG line chart for a property's price history.
import "@f-ewald/components/price-history-chart.js";
const el = document.querySelector("price-history-chart");
el.history = [
{ date: "2023-01-01", price: 620000, eventType: "Listed" },
{ date: "2023-06-01", price: 645000, eventType: "Price change" },
{ date: "2024-02-01", price: 680000, eventType: "Sold" },
];
radio-cards
Single-select cards with a label and optional description.
Selected:
import "@f-ewald/components/radio-cards.js";
const el = document.querySelector("radio-cards");
el.options = [
{ value: "simple", label: "Simple", description: "Quick-ranking view" },
{ value: "detailed", label: "Detailed", description: "Every section and layer" },
];
el.value = "simple";
el.addEventListener("change", (e) => console.log(e.detail.value));
radio-pills
Single-select compact pills for many short, same-shaped options.
Selected:
import "@f-ewald/components/radio-pills.js";
const el = document.querySelector("radio-pills");
el.options = [
{ value: "light", label: "Light" },
{ value: "streets", label: "Streets" },
{ value: "satellite", label: "Satellite" },
];
el.value = "light";
el.addEventListener("change", (e) => console.log(e.detail.value));
relative-time
Inline relative-time display (e.g. "3 hours ago").
import "@f-ewald/components/relative-time.js";
<relative-time datetime="2026-07-17T07:00:00Z"></relative-time>
roman-numeral
Converts an integer to a roman numeral inline.
import "@f-ewald/components/roman-numeral.js";
<roman-numeral value="2004"></roman-numeral>
slide-panel
Sliding panel shell with header chrome and a close button.
import "@f-ewald/components/slide-panel.js";
<slide-panel open heading="Property details">
Panel body content goes here.
</slide-panel>
stat-meter
Compact labeled meter for a single percentage reading — CPU/memory usage in a
dashboard header, for example. percent accepts null for
"no reading yet", rendering an empty bar and a "—" instead of "0%".
Custom fill color
import "@f-ewald/components/stat-meter.js";
<stat-meter label="CPU" percent="42"></stat-meter>
<stat-meter label="MEM" percent="76"></stat-meter>
<stat-meter label="I/O"></stat-meter> <!-- percent unset -> null -> renders "—" -->
<stat-meter label="GPU" percent="88" color="#dc2626"></stat-meter>
status-pill
Colored status pill, optionally with a spinning icon.
import "@f-ewald/components/status-pill.js";
<status-pill label="Running" color="primary" spinner></status-pill>
<status-pill label="Blocked" color="danger"></status-pill>
tile-grid
Generic grid shell: one bordered tile per items entry, content from
renderTile. Optional itemHref makes whole tiles clickable.
Optional file-icon prefixes each tile with a decorative "document" icon.
import "@f-ewald/components/tile-grid.js";
const grid = document.querySelector("tile-grid");
grid.items = [
{ name: "notes.txt" },
{ name: "photo.jpg" },
];
grid.renderTile = (item) => item.name;
grid.fileIcon = true; // or the `file-icon` attribute
toast-notification
Fixed-position stack of dismissible notifications.
import "@f-ewald/components/toast-notification.js";
import { notifySuccess } from "@f-ewald/components/toast-notification.js";
<toast-notification></toast-notification>
<script>notifySuccess("Saved!");</script>
user-avatar
Circular avatar — shows an image, falling back to an initial, falling back further to a generic icon.
import "@f-ewald/components/user-avatar.js";
<user-avatar src="https://example.com/photo.jpg" name="Freddy" size="40"></user-avatar>
<user-avatar name="Freddy" size="sm"></user-avatar>
weight-bar-chart
Sorted horizontal bar chart of normalized weights.
import "@f-ewald/components/weight-bar-chart.js";
document.querySelector("weight-bar-chart").items = [
{ id: "price", label: "Price", value: 0.4 },
{ id: "schools", label: "Schools", value: 0.35 },
{ id: "commute", label: "Commute", value: 0.25 },
];