Try it Pick a colour, then switch emphasis to high.
Reporting
Colour ghost accent success warning error ultra neutral live
Capitals
<WildBadge
color="success"
emphasis="low"
size="xs"
>
Reporting
</WildBadge>Copy When to use Use a badge to label a row with a short status or category: Online, Pending, Low battery. Use a badge for a fact and a chip for something a person chose. A badge is a small rounded rectangle; a chip has fully round ends. Use low emphasis by default, and high only for the row that has to be found while scrolling. Don’t use it
For anything a person can press, toggle or remove. Use a chip . As the only sign of a status. The words carry the meaning; the colour backs them up. Colours Eight colours, each at two emphasis levels.
Show codeCopy <WildBadge color ="success" >Online</WildBadge >
<WildBadge color ="warning" emphasis ="high" >Low battery</WildBadge >Use ultra for states that are notable but neither good nor bad, such as a site in calibration or a reading held for review. Emphasis Low is a tint with coloured text; high is a solid fill. Ghost and neutral share one high fill, so they’re left out below.
low
high
accent
Online
Online
success
Online
Online
warning
Online
Online
error
Online
Online
ultra
Online
Online
live
Online
Online
Keep high for the one row that needs finding, such as an offline logger or a failed sync. Used everywhere, it stops meaning anything. Sizes Live adds a pulsing dot. It holds still when the reader has reduced motion turned on. Accessibility A badge isn’t focusable, and a screen reader reads its label as text. Every colour clears 4.5:1 at both emphasis levels but one: a high-emphasis live badge is white on blue-1000, accepted at 4.23:1.
Pass a label even with iconOnly. It’s hidden on screen and kept for screen readers. Props Prop Type Default Description color 'ghost' | 'accent' | 'success' | 'warning' | 'error' | 'ultra' | 'neutral' | 'live' 'ghost' The status or category colour. emphasis 'low' | 'high' 'low' A tint with coloured text, or a solid fill. size '2xs' | 'xs' | 'sm' 'xs' 20, 24 or 28px tall. capitals boolean false Uppercase, with 0.5px letter spacing. live boolean false Green fill with a pulsing dot. icon ReactNode — Icon before the label. iconOnly boolean false Square badge; the label is kept for screen readers. children ReactNode — The label. One or two words.
Tokens Token Value Where it lands --radius-md 6px Corners. --font-weight-medium 475 Label weight. --color-primary-light #EBFAFF Accent ground. --global-blue-1100 #0C296A Accent text, 12.75:1 on the accent ground. --color-primary #00AEEF Accent high fill, under #0A090B text. --global-green-100 #EBFBF1 Success ground, and the live fill. --global-green-900 #018030 Success text and high fill. --global-red-900 #BC1C1C Error text. --color-error #E12121 Error high fill. --color-warning #EC980C Warning high fill, under #0A090B text. --color-success #019939 The live dot.
Try it Click the chip to select it, and the cross to remove it.
Variant outline accent success warning error
Selected
Removable
Disabled
<WildChip
variant="outline"
size="md"
removable
>
Bore 12
</WildChip>Copy When to use Use a chip for something a person chose and can change: a filter, a recipient, a selected site. Use a chip for a choice and a badge for a fact. A chip has fully round ends; a badge is a small rounded rectangle. Use outline by default. Keep the colour variants for a filter on that state, such as Offline on error. Don’t use it
To label a status nobody can change. Use a badge . Variants Five. Outline is the default and covers most uses.
Show codeCopy <WildChip variant ="outline" removable>Bore 12</WildChip >
<WildChip variant ="accent" selected>North paddock</WildChip >States
rest
hover
active
focus
outline
Bore 12
Bore 12
Bore 12
Bore 12
accent
Bore 12
Bore 12
Bore 12
Bore 12
success
Bore 12
Bore 12
Bore 12
Bore 12
warning
Bore 12
Bore 12
Bore 12
Bore 12
error
Bore 12
Bore 12
Bore 12
Bore 12
selected
Selected is a value, so it stays when the pointer leaves. Hover deepens a selected chip’s fill, so it still reads as pressable. Remove button The × takes the chip’s own colour, and hovering it fills a circle in the chip’s light step. On a selected chip it takes the chip’s ink.
Show codeCopy <WildChip variant ="success" removable>Reporting</WildChip >Point at each ×. The circle sits inside a 24px target, so pressing it never touches the chip’s edge. Accessibility A selectable chip is a toggle button, so a screen reader hears its label and whether it’s pressed. The cross is a separate button named “Remove” plus the label.
Key What it does Tab Moves to the chip, then to its cross. Enter / Space Toggles the chip, or removes it from the cross.
Props Prop Type Default Description variant 'outline' | 'accent' | 'success' | 'warning' | 'error' 'outline' Outline, or the state a filter is on. size 'sm' | 'md' | 'lg' 'md' 28, 32 or 36px tall. selected boolean false Filled, with a tick. onSelect (next: boolean) => void — Makes the chip a toggle. Receives the next value. removable boolean false Draws the remove cross. onDismiss () => void — Called when the cross is pressed. disabled boolean false Dims the chip and blocks both actions. avatar ReactNode — Leading avatar, for a person. icon ReactNode — Leading icon. Replaces the tick. trailingIcon ReactNode — Trailing icon, such as a menu caret. children ReactNode — The label.
Tokens Token Value Where it lands (no token) 24px Corners. --color-border-default #E6E6E6 Outline border. --color-primary-light #EBFAFF Accent ground. --global-blue-1100 #0C296A Accent text; selected fill on hover. --global-blue-1000 #1877F2 Selected fill, under white text. --global-blue-1000 #1877F2 Focus ring, outside a 2px gap. --global-{family}-1000 / 1100 the chip’s family The × at rest, then on hover. --global-{family}-200 / 300 the chip’s family The × circle on hover, then pressed. --global-neutral-grey-1000 / 1300 #4F4D55 / #0A090B Outline chip’s ×, rest and hover. --global-neutral-grey-300 / 400 #F1F1F1 / #ECECED Outline chip’s × circle. (no token) white 24% / 36% Selected chip’s × circle, hover and pressed. (no token) #0A090B 12% / 20% The same circle on the amber fill. --font-weight-semibold 500 Label weight.
Try it Turn on the dot, then step down through the sizes.
Family blue green orange red purple neutral
Square
Dot
<WildAvatar
size="md"
color="blue"
src="/team/aroha.jpg"
/>Copy Show the dot at 32px and up. It never shrinks below 8px, so on a smaller avatar it covers a third or more. When to use Use an avatar to mark a person or organisation in a list: an activity row, a table cell, a stacked group. Use round for people and square for organisations and sites. Don’t mix the two in one list. Use the dot for presence only. It has one meaning: online. Don’t use it
As the only label. Put the name in text beside it. To show a status. Use a badge . Sizes Eight, from 16 to 64px, so one avatar works in a table cell and on a profile screen.
Show codeCopy <WildAvatar size ="sm" initials ="JM" color ="blue" />
<WildAvatar size ="md" src ="/team/jm.jpg" alt ="Joel Mann" />Keep the three smallest for dense rows. Below 24px only the first initial is drawn, because two letters at 16px can’t be read. Colour families Six. Pick the family from the name, so the same person is the same colour on every screen.
Each family is a light tint under a dark ink, so initials stay readable at 16px. Shape and presence The dot is a quarter of the avatar with a 2px white ring, so it shows on every family. Square is for organisations and sites; round is for people. Accessibility An avatar isn’t focusable, and its initials are read out as letters. The dot is announced as “Online”.
Keep the person’s name in text beside the avatar. Give a photo an alt, or leave it empty when the name sits beside it. Props Prop Type Default Description size '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' 'md' 16, 20, 24, 32, 40, 48, 56 or 64px. initials string — One or two letters. Only the first shows below 24px. src string — Photo URL. Replaces the initials. alt string '' Text alternative for the photo. color 'blue' | 'green' | 'orange' | 'red' | 'purple' | 'neutral' 'blue' Colour family behind the initials. square boolean false 8px corners, for organisations and sites. dot boolean false Green presence dot, announced as Online. style CSSProperties — Merged onto the outer box.
Tokens Token Value Where it lands --radius-full 9999px The round shape. --radius-lg 8px The square shape. --color-primary-light #EBFAFF Blue family ground. --global-blue-1100 #0C296A Blue family initials, 12.75:1 on the blue ground. --color-grey-900 #4F4D55 Neutral family initials. --color-success #019939 The presence dot. --font-weight-semibold 500 Initials.
Try it Swap the contents, the padding and the header.
Sign in Enter your email and password.
Contents sign-in settings summary
Header
Wider
<WildCard title="Sign in" subtitle="Enter your email and password.">
<WildFormField label="Email"><WildInput v-model="email" /></WildFormField>
<WildFormField label="Password"><WildInput v-model="password" /></WildFormField>
<WildButton variant="primary">Sign in</WildButton>
</WildCard>Copy When to use Use a card to group content that belongs to one job: a form, a settings panel, a single figure. Use one card per job, so each surface can be read on its own. Set its width from the layout. A card fills the width it’s given. Don’t use it
For a reading on the dashboard. Use a widget . For content that needs a decision before the user carries on. Use a dialog . For rows of records. Use a table . Anatomy A white surface with a soft shadow. The header and footer are optional, and a rule splits each from the body.
Alarm notifications Choose how alarms reach you.
Email
Sent to the address on the account.
SMS
Sent to the mobile on the account.
Show codeCopy <WildCard title ="Alarm notifications" subtitle ="Choose how alarms reach you." >
<div >Email<WildToggle v-model ="email" /></div >
<div >SMS<WildToggle v-model ="sms" /></div >
<div >In-app<WildToggle v-model ="inApp" /></div >
<template #footer><WildButton >Save</WildButton ></template >
</WildCard >The body adds no layout of its own. Space the contents with their own wrapper. Actions 1,284 142 more than this time last month.
a status in actions
Put a status or a menu in actions, so it lines up with the title. Padding Each label gives the body inset as top and bottom / sides. The header and footer keep theirs.
1,284 142 more than this time last month.
View sites
sm · 16 / 20px
1,284 142 more than this time last month.
View sites
md · 20 / 24px · default
1,284 142 more than this time last month.
View sites
lg · 28 / 32px
Without a header or footer, that edge of the body is 24px, whatever the padding. Use none for edge-to-edge contents between a header and footer. Content Name the job in the title, in sentence case: Alarm notifications. Keep the subtitle to one line saying what to do here. Keep actions to one or two controls, such as a status or a menu. Accessibility A card is a plain container with no role. Its title is a level-3 heading, so screen readers can jump to it.
Key What it does Tab Moves through the controls inside. The card adds no stop.
Props Prop Type Default Description title string — Header title, rendered as a level-3 heading. subtitle string — One line under the title. actions ReactNode — Controls at the right of the header. footer ReactNode — Content below the body, after a rule. padding 'none' | 'sm' | 'md' | 'lg' 'md' Body inset. See Padding. children ReactNode — The body. The card never styles it. style CSSProperties — Merged onto the surface. Set width here.
Tokens Token Value Where it lands --card-bg #FFFFFF The surface. --radius-2xl 12px Every corner. (no token) 0 4px 24px -4px …, 0 0 0 1px … Elevation, the same shadow widgets use. --color-border-default #E6E6E6 Header and footer rules. --spacing-5 / --spacing-6 20px / 24px Header: 20px top and bottom, 24px sides. --spacing-4 / --spacing-6 16px / 24px Footer: 16px top and bottom, 24px sides. --font-size-xl 18px Title, at weight 500. --color-text-primary #0A090B Title colour. --font-size-md 13px Subtitle. --color-text-secondary #4F4D55 Subtitle colour.
Try it Change the number of series and add a reference to see which colour each key takes.
Block 3 Block 7 Block 12Last 7 days
20 25 30 35 40
34.7% Block 3 27.2% Block 7 38.1% Block 12
23 Sep 24 Sep 25 Sep 26 Sep 27 Sep 28 Sep 29 Sep
<Legend
items={[
{ label: 'Block 3' },
{ label: 'Block 7' },
{ label: 'Block 12' },
]}
period="Last 7 days"
/>Copy When to use Use a legend above a chart with two or more series. A lone series needs none, because the card title names it. With four series or fewer, also turn on the chart’s end labels, so each line is named where it ends. Add a period on the right when the chart covers a time range. Use a hollow key (dashed) for a reference series, such as a farm average, to match its hollow dots. Don’t use it
To switch what the chart shows. Keys aren’t buttons, so use a Segmented controller . For more than five series. The ramp has five colours, so split the chart or use a Table . Anatomy A key and a label for each series, then an optional period on the right. On a narrow card the keys wrap to a second line.
Block 3 Block 7 Block 12Last 7 days
keys, labels and period
Block 3 Block 7 Block 12 Block 15Last 7 days
wrapped on a narrow card
Show codeCopy <Legend
items ={[
{ label: 'Block 3' } ,
{ label: 'Block 7' } ,
{ label: 'Block 12' } ,
]}
period ="Last 7 days"
/>Colours Leave out color and solid keys take the series ramp in order, as the line chart’s lines do. A reference key is hollow and grey, so it stands out by its shape as well as its colour.
Block 3 Block 7 Block 12 Block 15 Block 21
five series
Block 3 Farm average
with a reference
Show codeCopy <Legend
items ={[
{ label: 'Block 3' } ,
{ label: 'Block 7' } ,
{ label: 'Block 12' } ,
{ label: 'Block 15' } ,
{ label: 'Block 21' } ,
]}
/>
<Legend
items ={[
{ label: 'Block 3' } ,
{ label: 'Farm average', dashed: true } ,
]}
/>Pass color when the chart sets its own, so each key matches its line. Content Name series that measure the same thing by what differs between them: Block 3, 10 cm, Farm average. Keep the period short: Today, Last 7 days, 7-day forecast. Use sentence case, and keep each label to a few words so the keys fit on one line. Accessibility A screen reader reads each label in order, then the period, and the keys are silent. Give the chart an ariaLabel that names the same series.
Key What it does Tab Passes over the legend, which has nothing to operate.
Props Prop Type Default Description items { label, color?, dashed? }[] — One key per series, in the chart’s order. items[].label string — The series name. See Content for when to add a unit. items[].color string ramp or grey Key colour. Leave out to match the line chart. items[].dashed boolean false A hollow grey key, for a reference series. period string — The time range, on the right.
Tokens Token Value Where it lands SERIES_RAMP #1877F2 Solid keys, in order; blue-1000 first. SERIES_REFERENCE #7F7D83 A reference key, grey-900. 4.1:1. T.legend #2D2B32 Labels, grey-1100. T.ink4 #4F4D55 The period, grey-1000. --font-size-xs 11px Labels and the period. --font-weight-bold 600 Labels. --font-weight-semibold 500 The period. (no token) 9px, 3px corners The key. (no token) 2px The dashed key’s outline. --spacing-1-5 6px Key to label. --spacing-3-5 14px Between keys. --spacing-2 8px Between wrapped lines. --spacing-3 12px Below the legend, above the plot.
Table Shows rows of readings, devices or records, with sorting, selection and three densities.
Try it Change the density and the ground, then click a header to sort.
Site Last Reading Soil % Target % Gap % Bore 12 2m ago 34.1 50.0 −15.9 Bore 7 4m ago 48.9 50.0 −1.1 Bore 3 3m ago 61.2 50.0 +11.2 Ridge Tank 11m ago 72.4 60.0 +12.4 South Pivot 1h ago 28.6 45.0 −16.4 North Paddock Mar 14, 2026 — No value 45.0 — No value
Density compact default comfortable
Ground plain striped bordered
Selectable
Tone
<TableRoot density="default">
<Table minWidth={520}>
<TableHeader>…</TableHeader>
<TableBody>
<TableRow>
<TableCell>Bore 12</TableCell>
<TableCell numeric tone="bad">−15.9</TableCell>
</TableRow>
</TableBody>
</Table>
</TableRoot>Copy Density changes the row height only. The header stays at 40px. When to use Use a table to compare readings, devices or records across the same columns. Use it when people sort, scan or select rows. Pair it with Pagination when the rows run past one screen. Don’t use it
For a few values about one site. Use a Card . For a reading over time. Use a Line chart . Anatomy Set striped, bordered and interactive once on TableBody, so every row matches.
Site Last Reading Soil % Target % Gap % Bore 12 2m ago 34.1 50.0 −15.9 Bore 7 4m ago 48.9 50.0 −1.1 Bore 3 3m ago 61.2 50.0 +11.2 Ridge Tank 11m ago 72.4 60.0 +12.4 South Pivot 1h ago 28.6 45.0 −16.4 North Paddock Mar 14, 2026 — No value 45.0 — No value 5 of 6 sites reporting
Show codeCopy <TableRoot density ="default" >
<Table >
<TableColgroup ><TableCol width ="15%" /></TableColgroup >
<TableHeader >
<TableRow ><TableHead numeric>Soil %</TableHead ></TableRow >
</TableHeader >
<TableBody striped interactive>…</TableBody >
<TableFooter >…</TableFooter >
</Table >
</TableRoot >Sorting and selection Click a header, then tick some rows.
Site Last Reading Soil % Target % Gap % Bore 12 2m ago 34.1 50.0 −15.9 Bore 7 4m ago 48.9 50.0 −1.1 Bore 3 3m ago 61.2 50.0 +11.2 Ridge Tank 11m ago 72.4 60.0 +12.4 South Pivot 1h ago 28.6 45.0 −16.4 North Paddock Mar 14, 2026 — No value 45.0 — No value
No rows selected · 6 shown
Sorting cycles ascending, descending, then off. Empty values stay at the bottom. Tone Tone a cell when the number is the finding, such as the gap to target.
Site Last Reading Soil % Target % Gap % Bore 12 2m ago 34.1 50.0 −15.9 Bore 7 4m ago 48.9 50.0 −1.1 Bore 3 3m ago 61.2 50.0 +11.2 Ridge Tank 11m ago 72.4 60.0 +12.4 South Pivot 1h ago 28.6 45.0 −16.4 North Paddock Mar 14, 2026 — No value 45.0 — No value
Empty and unknown values Pass an empty state to empty on TableRoot. The header stays.
Site Last Reading Soil % Target % Gap %
No sites match this filter Six sites are hidden by the current filter. Clear filter A cell with no value shows an em dash, and a screen reader hears “No value”. Content Write headers as short nouns in Title Case: Site, Last Reading. Put the unit in the header, so cells hold only the figure: Soil %. Give every value in a column the same decimal places. Accessibility A sortable header announces the order it will switch to. Name each row’s checkbox after the row, and announce the selected count.
Key What it does Tab Moves through sort headers, select-all and row checkboxes. Enter / Space Changes the sort, or ticks a row’s checkbox.
Props Prop Type Default Description TableRoot density 'compact' | 'default' | 'comfortable' 'default' Row height: 32, 40 or 48px. TableRoot maxHeight number — Scrolls rows under a sticky header. TableRoot empty ReactNode — Shown under the header. Table selectable boolean false Sets aria-selected on rows. TableCheck announces the state. Table minWidth number — Scrolls sideways below this width. TableCol width number | string — Column width, in px or percent. TableBody striped boolean false Shades every second row. TableBody bordered boolean false Rules between rows and cells. TableBody interactive boolean false Hover ground for clickable rows. TableRow selected boolean false Blue tint and a 3px start bar. TableRow onClick () => void — Pointer shortcut. Pair with TableCheck. TableHead sort 'asc' | 'desc' | 'none' — Sets the caret and aria-sort. TableHead onSort () => void — Makes the whole header cell a sort button. TableHead label string — Sort button name when children aren’t text. TableHead numeric boolean false Right-aligns the label. TableCell numeric boolean false Right-aligns, with tabular figures. TableCell tone 'ok' | 'note' | 'act' | 'bad' | 'live' | 'idle' — Colours the value as a finding. TableCell muted boolean false Secondary ink. TableCheck checked boolean — The ticked state. TableCheck onToggle () => void — Runs on click, Enter or Space. TableCheck label string — Accessible name, such as “Select Bore 12”. TableCheck indeterminate boolean false Dash for a partial selection.
Tokens Token Value Where it lands --spacing-8 / -10 / -12 32 / 40 / 48px Row heights. --spacing-3 12px Cell padding, left and right. --font-size-md 13px Cell values. --font-size-sm 12px Column headers. --font-weight-medium 475 Headers and numbers. --color-grey-100 #F5F5F5 Striped rows, header hover. --color-grey-200 #F1F1F1 Row hover. --color-primary-light #EBFAFF Selected row. --color-primary-active #1877F2 Selected bar, active caret, focus ring. --global-neutral-grey-900 #7F7D83 Sort caret at rest. --color-grey-300 #ECECED Cell rules when bordered. --color-border-default #E6E6E6 Outer hairline and header rule. --radius-lg 8px Outer corners.
Try it Change the status, then turn the pulse and label off.
Online
Status online warning error offline info success
Pulse
Label
<WildStatusIndicator
status="online"
size="md"
/>Copy When to use Use it for a condition that changes by itself, such as whether a logger is reporting. Use the small size at the start of a table or list row, so every row gets one. Use the pulse for one headline status. Turn it off in lists. Don’t use it
For a fixed category, such as a site type. Use Badge . For something that needs a reply. Use Alert . For how far a task has got. Use Progress . States Six statuses. Online, warning and error pulse by default.
Show codeCopy <WildStatusIndicator status ="online" />
<WildStatusIndicator status ="warning" />
<WildStatusIndicator status ="error" />
<WildStatusIndicator status ="offline" />
<WildStatusIndicator status ="info" />
<WildStatusIndicator status ="success" />Sizes Three dots: 8, 12 and 16px. The default is md.
Show codeCopy <WildStatusIndicator status ="online" size ="sm" :pulse="false" />
<WildStatusIndicator status ="online" size ="md" :pulse="false" />
<WildStatusIndicator status ="online" size ="lg" :pulse="false" />Device rows For a list of devices, use WStatusRow. Give it onClick when the row opens the device.
Tank sensor 2 Error · last reading 1 hour ago Bore 12 Offline · last reading 3 days ago Weather station 4 Warning · last reading 2 hours ago Bore 7 Online · last reading 4 minutes ago
Show codeCopy <WStatusRow
first
status ="offline"
title ="Bore 12"
meta ="Offline · last reading 3 days ago"
onClick ={openDevice}
/>Start every meta line with the status word and end it with a time, so the status still reads without colour. Content Name the condition in one or two words, such as Online or Offline. Put the time since the last reading beside it. Use sentence case. Accessibility The dot is hidden from screen readers and the word is read, or the label when showLabel is off. The pulse stops when reduced motion is on.
Key What it does Tab Moves focus to a row that has onClick. Enter / Space Opens the row.
Props Prop Type Default Description status 'online' | 'offline' | 'warning' | 'error' | 'info' | 'success' — The condition. Sets the colour and default label. size 'sm' | 'md' | 'lg' 'md' Dot of 8, 12 or 16px. label string — Replaces the default label. pulse boolean true Halo on online, warning and error. showLabel boolean true Shows the word. Off, it becomes the accessible name. children ReactNode — Replaces the label text. The Vue default slot.
WStatusRow A device row: the dot, a name and a meta line.
Prop Type Default Description status StatusKind — The condition, as above. title string — The device name. meta string — The status word and time since the last reading. trailing ReactNode — Optional content at the right edge, such as a badge. onClick () => void — Makes the row a button with hover and focus. state 'rest' | 'hover' | 'active' | 'focus' — Pins a state for static examples. Omit for live use. first boolean false Drops the top divider. Set it on the first row. pulse boolean false Halo on the dot. Off in lists.
Tokens Token Value Where it lands --global-green-800 #019939 Online and success. --global-squash-1000 #9D6508 Warning. --global-red-800 #E12121 Error. --global-blue-1000 #1877F2 Info, and the row focus ring. --global-neutral-grey-900 #7F7D83 Offline. --global-neutral-grey-1000 #4F4D55 Label. --global-green-300 #C1F4D4 Online pulse halo. --global-squash-300 #FFE9BA Warning pulse halo. --global-red-300 #FFC9C9 Error pulse halo. --global-neutral-grey-1300 #0A090B Row title. --global-neutral-grey-1100 #2D2B32 Row meta line. --global-neutral-grey-300 #F1F1F1 Row hover. --global-neutral-grey-400 #ECECED Row press and divider. --radius-full 9999px The dot.
Activity feed A day-grouped history of alarms, reports and integrations, with filters, collapsed repeats and held arrivals.
Try it Filter it, turn on Problems only, simulate an event, and move between rows with the arrow keys.
Activity All Alarms Reports Integrations Calculations
Problems only Today 7
Alarm SMS, failed: Low battery alarm SMS to Sam Okonkwo
The carrier rejected the number. Check the contact’s phone.
12m ago 12 minutes ago, Tue 29 Sep 2026, 11:28 am Alarm email, succeeded: High pressure alarm emailed to 3 people ×3 , 3 times
North Pump, 840ms , Pressure over 250 kPa
25m ago 25 minutes ago, Tue 29 Sep 2026, 11:15 am Report, succeeded: Weekly irrigation report sent
SO Sam Okonkwo , 12.4s , Attachment 2h ago 2 hours ago, Tue 29 Sep 2026, 9:40 am Webhook, warning: Readings pushed to Hortplus
8.2s , hortplus.com/ingest
Answered in 8.2 s, over the 5 s budget.
3h ago 3 hours ago, Tue 29 Sep 2026, 8:40 am Calculation, info: Evapotranspiration recalculated for 24 sites
5h ago 5 hours ago, Tue 29 Sep 2026, 6:40 am Yesterday 3
CSV export, succeeded: Soil moisture exported
MT Mere Tane , 2.3s , 3 depths · 30 days
Yesterday yesterday, Mon 28 Sep 2026, 9:40 am API call, failed: Reading sync from the Emu Park gateway
The gateway has been unreachable for 42 minutes.
Yesterday yesterday, Mon 28 Sep 2026, 7:40 am Data import, succeeded: Imported 1,240 readings from CSV
Yesterday yesterday, Mon 28 Sep 2026, 4:40 am This week 2
Alarm email, succeeded: Tank low alarm emailed to 2 people
Tank sensor 2, 650ms , Level under 20%
3d ago 3 days ago, Sat 26 Sep 2026, 10:40 am Report, pending: Monthly compliance report
4d ago 4 days ago, Fri 25 Sep 2026, 10:40 am Older 1
Webhook, succeeded: Readings pushed to Hortplus
1.2s , hortplus.com/ingest
20 Sep 20 Sep, Sun 20 Sep 2026, 10:40 am Show older activity
Simulate an event New events wait behind a pill until you show them.
Density comfortable compact
Collapse repeats
<WActivityFeed
items={executions}
now={now}
density="comfortable"
filter={family}
onFilterChange={setFamily}
failuresOnly={problemsOnly}
onFailuresOnlyChange={setProblemsOnly}
newCount={held.length}
onShowNew={releaseHeld}
onItemClick={openExecution}
renderActions={rowActions}
hasMore
onLoadMore={loadOlder}
/>Copy When to use Use it when several kinds of event share one history, such as alarms beside reports. Use it where someone checks that things ran, or looks for the one that didn’t. Don’t use it
For one event on its own. Use Activity item . For work still running, such as an irrigation run. Use Progress bar . For one message about the whole page. Use Alert . Grouping Rows group under Today, Yesterday, This week and Older, each with a count. A rail joins the rows of a group, so the list reads as a sequence.
Activity
Today 7
Alarm SMS, failed: Low battery alarm SMS to Sam Okonkwo
The carrier rejected the number. Check the contact’s phone.
12m ago 12 minutes ago, Tue 29 Sep 2026, 11:28 am
Alarm email, succeeded: High pressure alarm emailed to 3 people ×3 , 3 times
North Pump, 840ms , Pressure over 250 kPa
25m ago 25 minutes ago, Tue 29 Sep 2026, 11:15 am
Report, succeeded: Weekly irrigation report sent
SO Sam Okonkwo , 12.4s , Attachment 2h ago 2 hours ago, Tue 29 Sep 2026, 9:40 am
Webhook, warning: Readings pushed to Hortplus
8.2s , hortplus.com/ingest
Answered in 8.2 s, over the 5 s budget.
3h ago 3 hours ago, Tue 29 Sep 2026, 8:40 am
Calculation, info: Evapotranspiration recalculated for 24 sites
5h ago 5 hours ago, Tue 29 Sep 2026, 6:40 am
Yesterday 3
CSV export, succeeded: Soil moisture exported
MT Mere Tane , 2.3s , 3 depths · 30 days
Yesterday yesterday, Mon 28 Sep 2026, 9:40 am
API call, failed: Reading sync from the Emu Park gateway
The gateway has been unreachable for 42 minutes.
Yesterday yesterday, Mon 28 Sep 2026, 7:40 am
Data import, succeeded: Imported 1,240 readings from CSV
Yesterday yesterday, Mon 28 Sep 2026, 4:40 am
This week 2
Alarm email, succeeded: Tank low alarm emailed to 2 people
Tank sensor 2, 650ms , Level under 20%
3d ago 3 days ago, Sat 26 Sep 2026, 10:40 am
Report, pending: Monthly compliance report
4d ago 4 days ago, Fri 25 Sep 2026, 10:40 am
Older 1
Webhook, succeeded: Readings pushed to Hortplus
1.2s , hortplus.com/ingest
20 Sep 20 Sep, Sun 20 Sep 2026, 10:40 am
Back-to-back runs of one event, with the same description, site and outcome, become one row with a count. Runs a person started never merge. States Skeleton rows while loading. An empty feed says why it’s empty, and a filtered one offers to show everything.
Activity
No activity yet Alarm notifications, reports and integrations appear here as they run. nothing yet
No problems to show Everything in this period ran as expected. Show everything problems only, all clear
Content Describe what ran and who it reached: “High pressure alarm emailed to 3 people”. Put the site, the person and the target in their own fields, so the description stays short. Write failure messages as the next step: “Check the contact’s phone”. Accessibility Each row is announced as its type, its outcome and its description, then the details and the full time. Day labels are headings, and Problems only is a checkbox.
Key What it does Tab Moves to the chosen filter, Problems only, the new events pill, each row and its actions, then Show older activity. Left / Right In the filter, moves to the next or previous family and shows it. Space Turns Problems only on or off. Down / Up Moves to the next or previous row. Home / End Moves to the first or last row. In the filter, picks the first or last family. Enter / Space Opens the focused row. Escape Hides the time tooltip.
Props Prop Type Default Description items ActivityExecution[] — The events, in any order. The feed sorts them. now Date | number — One clock for every relative time. timeZone string — IANA zone for days and times. density 'comfortable' | 'compact' 'comfortable' Row height and type size. title string 'Activity' Toolbar title and the feed’s accessible name. filter 'all' | ProcessFamily 'all' Which family to show. onFilterChange (f) => void — Shows the family filter. failuresOnly boolean false Shows failures and warnings only. onFailuresOnlyChange (on) => void — Shows the Problems only checkbox. collapseRepeats boolean true Joins back-to-back identical events into one row. newCount number 0 Held-back arrivals, shown as a pill. onShowNew () => void — Releases the held-back arrivals. loading boolean false Skeleton rows in place of the list. onItemClick (item) => void — Makes each row a button. renderActions (item) => ReactNode — Row actions. Use WActivityAction. hasMore boolean false Shows the Show older activity button. onLoadMore () => void — Loads the next page. maxHeight number — Scrolls inside the feed, with sticky day labels.
Tokens Token Value Where it lands --global-neutral-grey-400 #ECECED Rail and toolbar rule. --global-neutral-grey-300 #F1F1F1 Rule after each day label. --global-neutral-grey-1100 #2D2B32 Day label, and new events pill hover. --global-neutral-grey-1000 #4F4D55 Day count. --global-neutral-grey-200 #F5F5F5 Day count chip. --global-neutral-grey-1300 #0A090B Toolbar title and new events pill. --global-blue-100 #EBFAFF Wash behind an arriving row. --global-blue-1000 #1877F2 Show older activity, and focus ring. --radius-3xl 16px Feed corners. --shadow-card 0 2px 12px -1px … Feed elevation.
Try it Remove a filter and the count widens with it. Tab into the row, then use the arrow keys and Backspace.
Filters Region: Bay of PlentySensor: Soil moistureAlarms: ActiveClear all 12 of 120 sites <WildFilterChips
:chips="activeFilters"
label="Filters"
:count="countLabel"
empty-text="No filters applied"
@remove="removeFilter"
@clear="clearFilters"
/>Copy Focus lands where the chip was. Remove one and the next chip takes the focus, or Clear all does when it was the last. When to use Use filter chips to show the filters narrowing a list, each one removable. Put them under the list’s title, with the count of what’s left. Don’t use it
Anatomy A label, the chips, Clear all and the count, on one line that wraps. Only the chips wrap; the label and count hold the ends.
Filters Region: Bay of PlentyCrop: KiwifruitAlarms: ActiveClear all 15 of 120 sites The field is in regular weight and the value in 500, so a row scans by what was chosen. Chip content A value on its own, a field and its value, and an optional icon.
Region: Bay of Plentyfield and value
Site: Te Puke Orchard, north block irrigation maintruncated
Add the field when values could be confused, as in “Alarms: Active”. Values past 240px truncate, and the tooltip carries the whole value. Sizes 28 under a table, 32 in a toolbar, 36 beside a large search field.
Region: Bay of PlentyAlarms: ActiveClear all sm · 28
Region: Bay of PlentyAlarms: ActiveClear all md · 32
Region: Bay of PlentyAlarms: ActiveClear all lg · 36
Text steps with the height: 12, 13 and 14px. The × target is 24px, and 28px at large. States The chip isn’t a button. Its states belong to the ×, and the chip answers them.
Region: Bay of Plentyrest
Region: Bay of Plentyhover
Region: Bay of Plentypressed
Region: Bay of Plentyfocus
Region: Bay of Plentydisabled
Hovering the × fills a blue circle behind it, a shade deeper when pressed. Focus rings the whole chip, because the chip is what goes. Filters Region: Bay of PlentySensor: Soil moistureClear all 43 of 120 sites disabled row
Filters No filters applied
120 sites no filters
Disabled locks the chips and leaves the count readable. With no filters, the row says so in secondary ink. In context Add filters from the button, remove them from the chips, and watch the rows change.
Filters Region: Bay of PlentySensor: Soil moistureClear all 43 of 120 sites Bore 12 Soil moisture 18 kPa · Kiwifruit · 64% battery Emu Park Soil moisture 55 kPa · Kiwifruit · 88% battery Omokoroa Bore 10 Soil moisture 38 kPa · Kiwifruit · 61% battery Pongakawa Bore 17 Soil moisture 75 kPa · Kiwifruit · Offline and 39 more “43 of 120 sites” says how much is hidden as well as what’s left. New chips scale in at the end of the row. Accessibility The row is one tab stop. A screen reader hears which filter went, and the new count.
Key What it does Tab Moves into the row, onto the chip focused last, then out. → / ← Moves between chips and Clear all, wrapping at the ends. Home / End Moves to the first or last item. Backspace / Delete Removes the focused chip. Enter / Space Removes the focused chip, or clears all from Clear all.
Props Prop Type Default Description chips { id, label, field?, icon? }[] — The filters, in the order they were added. size 'sm' | 'md' | 'lg' 'sm' 28, 32 or 36px tall. label string — A leading label, which also names the row. count string — The result count, announced when it changes. clear-label string 'Clear all' Shown from two chips up. empty-text string — Shown when there are no chips. aria-label string 'Active filters' The row’s name with no visible label. disabled boolean false Locks the chips and Clear all. @remove (id: string) => void — A chip’s ×, Backspace or Delete. @clear () => void — Clear all.
Tokens Token Value Where it lands --global-blue-100 #EBFAFF Chip ground. --global-blue-200 #D6F4FF The × circle on hover. --global-blue-300 #BDEDFF Chip edge; the × circle pressed. --global-blue-400 #A3E7FF Chip edge while the × is hovered. --global-blue-1100 #0C296A Chip text; the × on hover. --global-blue-1000 #1877F2 The × at rest; Clear all; focus. --global-neutral-grey-1000 #4F4D55 Count and empty text. (no token) 24px Chip radius.
Activity item One row of an activity feed: what ran, how it went, who started it, where and when.
Try it Hover the row and its time, or tab to it and press Enter.
Alarm SMS, failed: Low tank level alarm
North Pump, 3.0s , +64 21 555 0142
Carrier rejected the message: the number is not reachable
14m ago 14 minutes ago, Mon 28 Sep 2026, 9:27 am Click the row, or tab to it and press Enter.
Status success failure warning pending info
Process alarm-email alarm-sms report csv-export api-call webhook data-import calculation
Density comfortable compact
Clickable
Who
Site
Target
Actions
<WActivityItem
item={run}
now={now}
onClick={openRun}
actions={
<>
<WActivityAction label="Retry" icon={<ArrowPathIcon />} onClick={retry} />
<WActivityAction label="View log" icon={<DocumentTextIcon />} onClick={openLog} />
<WActivityAction label="More" icon={<EllipsisHorizontalIcon />} onClick={openMenu} />
</>
}
/>Copy When to use Use one row per process run: an alarm sent, a report built. Make it clickable when it opens the run’s log. Don’t use it
Anatomy Tile, words, actions and time. In a narrow row, the actions drop below the words.
Webhook, failed: Alarm webhook
SO Sam Okonkwo , North Pump, 5.0s , hooks.farmops.nz/wildeye
The endpoint returned 503 after 3 attempts
6m ago 6 minutes ago, Mon 28 Sep 2026, 9:35 am
Tile What ran. The glyph is the process; the colour is the family.
Status mark How it went, with a different shape for each status.
Description and meta The run’s name, then who, where, how long and to what.
Message Failures and warnings only, in the status ink.
Actions Appear over the row’s end on hover and focus. Pinned open here.
Time Relative, with the exact time on hover and focus. The target goes last, so it takes the space that’s left and truncates first. Process types Eight processes in four families, one colour each.
No family is red, green or amber, because those colours mean status. Statuses Five, each with its own mark shape.
Alarm email, succeeded: High soil moisture alarm
Emu Park, 1.2s , sam.okonkwo@emupark.co.nz
3m ago 3 minutes ago, Mon 28 Sep 2026, 9:38 am
Alarm SMS, failed: Low tank level alarm
North Pump, 3.0s , +64 21 555 0142
Carrier rejected the message: the number is not reachable
14m ago 14 minutes ago, Mon 28 Sep 2026, 9:27 am
API call, warning: Reseller readings sync
Bore 12, 8.5s , api.agrilink.co.nz/v2/readings
Synced 118 of 120 readings; 2 rejected as duplicates
41m ago 41 minutes ago, Mon 28 Sep 2026, 9:00 am
Report, pending: Monthly rainfall report
Emu Park, rainfall-september.pdf
in 20m in 20 minutes, Mon 28 Sep 2026, 10:01 am
Data import, info: Logger firmware notes imported
PR Priya Raman , release-4.2.txt
1h ago 1 hour ago, Mon 28 Sep 2026, 8:06 am
Failures and warnings add an edge and a message, so they stand out down a feed. States A clickable row is a button with a hover ground and a tab stop.
API call, warning: Reseller readings sync
Bore 12, 8.5s , api.agrilink.co.nz/v2/readings
Synced 118 of 120 readings; 2 rejected as duplicates
41m ago 41 minutes ago, Mon 28 Sep 2026, 9:00 am rest
API call, warning: Reseller readings sync
Bore 12, 8.5s , api.agrilink.co.nz/v2/readings
Synced 118 of 120 readings; 2 rejected as duplicates
41m ago 41 minutes ago, Mon 28 Sep 2026, 9:00 am hover
API call, warning: Reseller readings sync
Bore 12, 8.5s , api.agrilink.co.nz/v2/readings
Synced 118 of 120 readings; 2 rejected as duplicates
41m ago 41 minutes ago, Mon 28 Sep 2026, 9:00 am focus-visible
API call, warning: Reseller readings sync
Bore 12, 8.5s , api.agrilink.co.nz/v2/readings
Synced 118 of 120 readings; 2 rejected as duplicates
41m ago 41 minutes ago, Mon 28 Sep 2026, 9:00 am pressed
API call, warning: Reseller readings sync
Bore 12, 8.5s , api.agrilink.co.nz/v2/readings
Synced 118 of 120 readings; 2 rejected as duplicates
41m ago 41 minutes ago, Mon 28 Sep 2026, 9:00 am plain row, hover
Actions sit beside the row’s button, never inside it. Density Comfortable for a full feed, compact for a narrow card.
Alarm SMS, failed: Low tank level alarm
North Pump, 3.0s , +64 21 555 0142
Carrier rejected the message: the number is not reachable
14m ago 14 minutes ago, Mon 28 Sep 2026, 9:27 am
Alarm email, succeeded: Low tank level alarm
SO Sam Okonkwo , North Pump, 1.2s , ops@northpump.co.nz
14m ago 14 minutes ago, Mon 28 Sep 2026, 9:27 am
API call, succeeded: Reseller readings sync ×4 , 4 times
Bore 12, 640ms , api.agrilink.co.nz/v2/readings
38m ago 38 minutes ago, Mon 28 Sep 2026, 9:03 am
comfortable
Alarm SMS, failed: Low tank level alarm
North Pump, 3.0s , +64 21 555 0142
Carrier rejected the message: the number is not reachable
14m ago 14 minutes ago, Mon 28 Sep 2026, 9:27 am
Alarm email, succeeded: Low tank level alarm
Sam Okonkwo , North Pump, 1.2s , ops@northpump.co.nz
14m ago 14 minutes ago, Mon 28 Sep 2026, 9:27 am
API call, succeeded: Reseller readings sync ×4 , 4 times
Bore 12, 640ms , api.agrilink.co.nz/v2/readings
38m ago 38 minutes ago, Mon 28 Sep 2026, 9:03 am
compact
Compact keeps each part to one line, and the target truncates first. Timestamps Alarm email, succeeded: Frost alarm
Just now just now, Mon 28 Sep 2026, 9:40 am
Webhook, succeeded: Alarm webhook
4m ago 4 minutes ago, Mon 28 Sep 2026, 9:37 am
API call, succeeded: Reseller readings sync
38m ago 38 minutes ago, Mon 28 Sep 2026, 9:03 am
Calculation, succeeded: Soil water deficit
5h ago 5 hours ago, Mon 28 Sep 2026, 4:31 am
Report, succeeded: Weekly irrigation report
Yesterday yesterday, Sun 27 Sep 2026, 1:41 pm
CSV export, succeeded: Season export
3d ago 3 days ago, Fri 25 Sep 2026, 8:11 am
Data import, succeeded: Rain gauge history import
12 Sep 12 Sep, Sat 12 Sep 2026, 8:41 am
Just now, minutes, then hours through today, then Yesterday, days, and the date after a week. In context A feed card built from rows, grouped by day.
Today CSV export, pending: Soil moisture readings export
SO Sam Okonkwo , Emu Park, emu-park-soil-sep.csv
Just now just now, Mon 28 Sep 2026, 9:40 am Alarm SMS, failed: Low tank level alarm
North Pump, 3.0s , +64 21 555 0142
Carrier rejected the message: the number is not reachable
14m ago 14 minutes ago, Mon 28 Sep 2026, 9:27 am Alarm email, succeeded: Low tank level alarm
North Pump, 1.2s , ops@northpump.co.nz
14m ago 14 minutes ago, Mon 28 Sep 2026, 9:27 am API call, succeeded: Reseller readings sync ×4 , 4 times
Bore 12, 640ms , api.agrilink.co.nz/v2/readings
38m ago 38 minutes ago, Mon 28 Sep 2026, 9:03 am Report, warning: Weekly irrigation report
Emu Park, 18.4s , Attachment , irrigation-weekly.pdfSent without rainfall; Weather station 4 has no readings since Friday
2h ago 2 hours ago, Mon 28 Sep 2026, 7:09 am Yesterday Calculation, succeeded: Soil water deficit
Emu Park, 220ms , Deficit (mm)
4:41 pm Sun 27 Sep 2026, 4:41 pm Data import, info: Rain gauge history import
PR Priya Raman , Emu Park, 9.8s , rainfall-2025.xml
1:29 pm Sun 27 Sep 2026, 1:29 pm Click a row, or tab to one and press Enter.
Under an earlier day’s heading, show clock times so the day isn’t repeated. Content Name the run: Low tank level alarm. Say in the message what happened and what fixes it. Accessibility Screen readers hear the process, status and description, then meta, message and time.
Key What it does Tab Moves to a clickable row, then into its actions. Shift + Tab Moves back. Enter / Space Opens the row, or runs the focused action. Escape Hides the time or action tooltip.
Props Prop Type Default Description item { id, type, status, description, timestamp, … } — The run. Also: target, siteName, duration, errorMessage, actor, count. item.type string — One of the eight process types. item.status string — One of the five statuses. showSite boolean true Shows the site. showTarget boolean true Shows the recipient, file or URL. density 'comfortable' | 'compact' 'comfortable' Compact: 24px tile, one line each. now Date | number mount time Clock relative times count from. Share one. timeZone string browser For calendar days and exact times. timeFormat 'relative' | 'clock' 'relative' Clock time, under a day heading. onClick (item) => void — Makes the whole row a button. actions ReactNode — WActivityAction buttons. revealActions 'hover' | 'always' 'hover' Always keeps them showing. Touch screens always show them. as 'div' | 'li' 'div' li inside a list. WActivityAction label string — Required. Accessible name and tooltip. WActivityAction icon ReactNode — 16px Heroicons glyph. WActivityAction disabled boolean false Greys out; stops the click.
Tokens Token Value Where it lands --global-neutral-grey-200 #F5F5F5 Hover ground. --global-neutral-grey-300 #F1F1F1 Pressed ground. --global-neutral-grey-400 #ECECED Initials disc. --global-neutral-grey-500 #E6E6E6 Tile outline, row rules. --global-neutral-grey-800 #ADACB0 Meta dots. --global-neutral-grey-900 #7F7D83 Info mark, paperclip. --global-neutral-grey-1000 #4F4D55 Meta, time, initials. --global-neutral-grey-1300 #0A090B Description. --global-blue-1000 #1877F2 Focus, pending mark, output glyphs. --global-green-800 #019939 Success mark. --global-red-800 #E12121 Failure mark, edge. --global-red-1000 #961616 Failure message. --global-squash-800 #EC980C Warning edge. --global-squash-900 #C47E0A Warning mark. --global-squash-1100 #764C06 Warning message. --global-violet-800 #6E23C9 Alarm glyphs. --global-teal-700 #0692BC Integration glyphs. --global-purple-900 #7B238D Calculation glyphs.