Data display

Badge

Labels a row or item with a short status or category.

Live React portVue source: WildBadge.vueStorybook story: Data Display/BadgeReact component: WBadge

Try it

Pick a colour, then switch emphasis to high.

Reporting
Colour
Emphasis
Size
Capitals
<WildBadge
  color="success"
  emphasis="low"
  size="xs"
>
  Reporting
</WildBadge>

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.

Online
ghost
Online
accent
Online
success
Online
warning
Online
error
Online
ultra
Online
neutral
Online
live
<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

Online
2xs · 20
Online
xs · 24
Online
sm · 28
Pending
capitals
Reporting
live
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

PropTypeDefaultDescription
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.
capitalsbooleanfalseUppercase, with 0.5px letter spacing.
livebooleanfalseGreen fill with a pulsing dot.
iconReactNode—Icon before the label.
iconOnlybooleanfalseSquare badge; the label is kept for screen readers.
childrenReactNode—The label. One or two words.

Tokens

TokenValueWhere it lands
--radius-md6pxCorners.
--font-weight-medium475Label weight.
--color-primary-light#EBFAFFAccent ground.
--global-blue-1100#0C296AAccent text, 12.75:1 on the accent ground.
--color-primary#00AEEFAccent high fill, under #0A090B text.
--global-green-100#EBFBF1Success ground, and the live fill.
--global-green-900#018030Success text and high fill.
--global-red-900#BC1C1CError text.
--color-error#E12121Error high fill.
--color-warning#EC980CWarning high fill, under #0A090B text.
--color-success#019939The live dot.

Chip

A compact item that can be selected or removed, used for filters and entered values.

Refined portVue source: WildChip.vueStorybook story: Data Display/ChipReact component: WChip

Try it

Click the chip to select it, and the cross to remove it.

Variant
Size
Selected
Removable
Disabled
<WildChip
  variant="outline"
  size="md"
  removable
>
  Bore 12
</WildChip>

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.

Bore 12
outline
Bore 12
accent
Bore 12
success
Bore 12
warning
Bore 12
error
<WildChip variant="outline" removable>Bore 12</WildChip>
<WildChip variant="accent" selected>North paddock</WildChip>

Sizes

Bore 12
sm · 28
Bore 12
md · 32
Bore 12
lg · 36

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
Bore 12
Bore 12
Bore 12
Bore 12
Selected is a value, so it stays when the pointer leaves. Hover deepens a selected chip’s fill, so it still reads as pressable.
North paddock
selected
Bore 12
disabled

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.

Bore 12
outline
Bore 12
accent
Bore 12
success
Bore 12
warning
Bore 12
error
North paddock
selected
<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.

KeyWhat it does
TabMoves to the chip, then to its cross.
Enter / SpaceToggles the chip, or removes it from the cross.

Props

PropTypeDefaultDescription
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.
selectedbooleanfalseFilled, with a tick.
onSelect(next: boolean) => void—Makes the chip a toggle. Receives the next value.
removablebooleanfalseDraws the remove cross.
onDismiss() => void—Called when the cross is pressed.
disabledbooleanfalseDims the chip and blocks both actions.
avatarReactNode—Leading avatar, for a person.
iconReactNode—Leading icon. Replaces the tick.
trailingIconReactNode—Trailing icon, such as a menu caret.
childrenReactNode—The label.

Tokens

TokenValueWhere it lands
(no token)24pxCorners.
--color-border-default#E6E6E6Outline border.
--color-primary-light#EBFAFFAccent ground.
--global-blue-1100#0C296AAccent text; selected fill on hover.
--global-blue-1000#1877F2Selected fill, under white text.
--global-blue-1000#1877F2Focus ring, outside a 2px gap.
--global-{family}-1000 / 1100the chip’s familyThe × at rest, then on hover.
--global-{family}-200 / 300the chip’s familyThe × circle on hover, then pressed.
--global-neutral-grey-1000 / 1300#4F4D55 / #0A090BOutline chip’s ×, rest and hover.
--global-neutral-grey-300 / 400#F1F1F1 / #ECECEDOutline 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-semibold500Label weight.

Avatar

Represents a person or organisation with initials, and optionally shows that they are online.

Refined portVue source: WildAvatar.vueStorybook story: Data Display/AvatarReact component: WAvatar

Try it

Turn on the dot, then step down through the sizes.

Content
Size
Family
Initials
Square
Dot
<WildAvatar
  size="md"
  color="blue"
  src="/team/aroha.jpg"
/>
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.

J
3xs · 16
J
2xs · 20
JM
xs · 24
JM
sm · 32
JM
md · 40
JM
lg · 48
JM
xl · 56
JM
2xl · 64
<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.

JM
blue
JM
green
JM
orange
JM
red
JM
purple
JM
neutral
Each family is a light tint under a dark ink, so initials stay readable at 16px.

Shape and presence

JM
round
JM
square
JM
dot
JM
square · dot
The dot is a quarter of the avatar with a 2px white ring, so it shows on every family.
WE
xs
WE
sm
WE
md
WE
lg
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

PropTypeDefaultDescription
size'3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl''md'16, 20, 24, 32, 40, 48, 56 or 64px.
initialsstring—One or two letters. Only the first shows below 24px.
srcstring—Photo URL. Replaces the initials.
altstring''Text alternative for the photo.
color'blue' | 'green' | 'orange' | 'red' | 'purple' | 'neutral''blue'Colour family behind the initials.
squarebooleanfalse8px corners, for organisations and sites.
dotbooleanfalseGreen presence dot, announced as Online.
styleCSSProperties—Merged onto the outer box.

Tokens

TokenValueWhere it lands
--radius-full9999pxThe round shape.
--radius-lg8pxThe square shape.
--color-primary-light#EBFAFFBlue family ground.
--global-blue-1100#0C296ABlue family initials, 12.75:1 on the blue ground.
--color-grey-900#4F4D55Neutral family initials.
--color-success#019939The presence dot.
--font-weight-semibold500Initials.

Card

A surface that groups related content, without adding any layout of its own.

Live React portVue source: WildCard.vueStorybook story: Data Display/CardReact component: WCard

Try it

Swap the contents, the padding and the header.

Sign in

Enter your email and password.

Email
sam@example.com
Password
••••••••••
Contents
Padding
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>

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.
In-app
<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

Sign in

Email
sam@example.com
Password
••••••••••
no actions

Sites reporting

+12%
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.

Sites reporting

1,284
142 more than this time last month.
sm · 16 / 20px

Sites reporting

1,284
142 more than this time last month.
md · 20 / 24px · default

Sites reporting

1,284
142 more than this time last month.
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.

KeyWhat it does
TabMoves through the controls inside. The card adds no stop.

Props

PropTypeDefaultDescription
titlestring—Header title, rendered as a level-3 heading.
subtitlestring—One line under the title.
actionsReactNode—Controls at the right of the header.
footerReactNode—Content below the body, after a rule.
padding'none' | 'sm' | 'md' | 'lg''md'Body inset. See Padding.
childrenReactNode—The body. The card never styles it.
styleCSSProperties—Merged onto the surface. Set width here.

Tokens

TokenValueWhere it lands
--card-bg#FFFFFFThe surface.
--radius-2xl12pxEvery corner.
(no token)0 4px 24px -4px …, 0 0 0 1px …Elevation, the same shadow widgets use.
--color-border-default#E6E6E6Header and footer rules.
--spacing-5 / --spacing-620px / 24pxHeader: 20px top and bottom, 24px sides.
--spacing-4 / --spacing-616px / 24pxFooter: 16px top and bottom, 24px sides.
--font-size-xl18pxTitle, at weight 500.
--color-text-primary#0A090BTitle colour.
--font-size-md13pxSubtitle.
--color-text-secondary#4F4D55Subtitle colour.

Pagination

Moves through long tables one page at a time.

Live React portVue source: WildPagination.vueStorybook story: Data Display/PaginationReact component: WPagination

Try it

Click a page number or a chevron, then try 128 pages and step through the middle.

Variant
Pages
Size
Count
Disabled
<WildPagination
  v-model="page"
  variant="numbered"
  :total-pages="20"
  :total-items="493"
  :items-per-page="25"
  size="md"
/>
Past seven pages the window always holds seven equal slots, ellipses included, so the controls keep their width as you page.

When to use

  • Use it under a table or list that runs past one page, such as readings or alarms.
  • Use numbered when people jump to a page, and simple when they only step.
  • Pass the item total and page size, so the count says where you are.
Don’t use it

Anatomy

The count, then the previous chevron, the page window and the next chevron.

first page
middle
no item total
<WildPagination v-model="page" variant="numbered"
  :total-pages="128" :total-items="3193" :items-per-page="25" />
Without total-items and items-per-page, the count reads “Page 7 of 128”.

Variants

Simple is the count and the chevrons. Numbered adds the page window between them.

simple
numbered

Sizes

Two. Use sm under a dense table, such as one inside a dialog or card.

md · 13px
sm · 12px

States

first page
last page
one page
disabled
On the first and last page, the chevron that would go nowhere is disabled but stays in place, so nothing beside it moves.

Accessibility

It’s a navigation landmark named Pagination. The chevrons are named Previous page and Next page, and the current page is announced as current.

KeyWhat it does
TabMoves through the chevrons and page numbers. Disabled controls are skipped.
Enter / SpaceGoes to that page. At the first or last page, focus moves to the current page, or to the other chevron in simple.

Props

PropTypeDefaultDescription
v-modelnumber—The current page, counting from 1.
total-pagesnumber—How many pages there are.
total-itemsnumber—Item total, for the count.
items-per-pagenumber—Page size, for the count.
variant'simple' | 'numbered' | 'compact''simple'Numbered adds page numbers; compact draws as simple.
size'sm' | 'md''md'Text at 12 or 13px; chevrons at 24 or 28px.
show-page-infobooleantrueShows the count before the controls.
disabledbooleanfalseBlocks every control.
@change(page: number) => void—Fires with the new page.

Tokens

TokenValueWhere it lands
--global-blue-1000#1877F2Current page fill, and the focus ring.
--global-neutral-grey-1000#4F4D55Page numbers, count and ellipsis.
--global-neutral-grey-900#7F7D83Chevrons at rest.
--global-neutral-grey-1200#1D1C20Page and chevron ink on hover.
--global-base-white#FFFFFFCurrent page number.
--global-neutral-grey-300#F1F1F1Page and chevron hover.
--global-neutral-grey-400#ECECEDPage and chevron press.
--global-neutral-grey-800#ADACB0Disabled chevrons and numbers.
--global-neutral-grey-200#F5F5F5Current page fill when disabled.
--font-weight-semibold500Page numbers.
--radius-md6pxPage number corners.

Legend

Names each series in a chart, so its colours can be read.

Live React portVue source: WildLegend.vueStorybook story: Data Display/LegendReact component: Legend

Try it

Change the number of series and add a reference to see which colour each key takes.

Block 3Block 7Block 12Last 7 days
Series
Reference
Period
<Legend
  items={[
    { label: 'Block 3' },
    { label: 'Block 7' },
    { label: 'Block 12' },
  ]}
  period="Last 7 days"
/>

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 3Block 7Block 12Last 7 days
keys, labels and period
Block 3Block 7Block 12Block 15Last 7 days
wrapped on a narrow card
<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 3Block 7Block 12Block 15Block 21
five series
Block 3Farm average
with a reference
<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.

KeyWhat it does
TabPasses over the legend, which has nothing to operate.

Props

PropTypeDefaultDescription
items{ label, color?, dashed? }[]—One key per series, in the chart’s order.
items[].labelstring—The series name. See Content for when to add a unit.
items[].colorstringramp or greyKey colour. Leave out to match the line chart.
items[].dashedbooleanfalseA hollow grey key, for a reference series.
periodstring—The time range, on the right.

Tokens

TokenValueWhere it lands
SERIES_RAMP#1877F2Solid keys, in order; blue-1000 first.
SERIES_REFERENCE#7F7D83A reference key, grey-900. 4.1:1.
T.legend#2D2B32Labels, grey-1100.
T.ink4#4F4D55The period, grey-1000.
--font-size-xs11pxLabels and the period.
--font-weight-bold600Labels.
--font-weight-semibold500The period.
(no token)9px, 3px cornersThe key.
(no token)2pxThe dashed key’s outline.
--spacing-1-56pxKey to label.
--spacing-3-514pxBetween keys.
--spacing-28pxBetween wrapped lines.
--spacing-312pxBelow the legend, above the plot.

Table

Shows rows of readings, devices or records, with sorting, selection and three densities.

Live React portReact component: TableRoot, Table, TableBody, TableRow, TableHead, TableCell, TableCheck

Try it

Change the density and the ground, then click a header to sort.

Last ReadingTarget %
Bore 122m ago34.150.0−15.9
Bore 74m ago48.950.0−1.1
Bore 33m ago61.250.0+11.2
Ridge Tank11m ago72.460.0+12.4
South Pivot1h ago28.645.0−16.4
North PaddockMar 14, 2026No value45.0No value
Density
Ground
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>
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.

SiteLast ReadingSoil %Target %Gap %
Bore 122m ago34.150.0−15.9
Bore 74m ago48.950.0−1.1
Bore 33m ago61.250.0+11.2
Ridge Tank11m ago72.460.0+12.4
South Pivot1h ago28.645.0−16.4
North PaddockMar 14, 2026No value45.0No value
5 of 6 sites reporting
<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.

Last ReadingTarget %
Bore 122m ago34.150.0−15.9
Bore 74m ago48.950.0−1.1
Bore 33m ago61.250.0+11.2
Ridge Tank11m ago72.460.0+12.4
South Pivot1h ago28.645.0−16.4
North PaddockMar 14, 2026No value45.0No 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.

SiteLast ReadingSoil %Target %Gap %
Bore 122m ago34.150.0−15.9
Bore 74m ago48.950.0−1.1
Bore 33m ago61.250.0+11.2
Ridge Tank11m ago72.460.0+12.4
South Pivot1h ago28.645.0−16.4
North PaddockMar 14, 2026No value45.0No value

Empty and unknown values

Pass an empty state to empty on TableRoot. The header stays.

SiteLast ReadingSoil %Target %Gap %
No sites match this filterSix sites are hidden by the current 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.

KeyWhat it does
TabMoves through sort headers, select-all and row checkboxes.
Enter / SpaceChanges the sort, or ticks a row’s checkbox.

Props

PropTypeDefaultDescription
TableRoot density'compact' | 'default' | 'comfortable''default'Row height: 32, 40 or 48px.
TableRoot maxHeightnumber—Scrolls rows under a sticky header.
TableRoot emptyReactNode—Shown under the header.
Table selectablebooleanfalseSets aria-selected on rows. TableCheck announces the state.
Table minWidthnumber—Scrolls sideways below this width.
TableCol widthnumber | string—Column width, in px or percent.
TableBody stripedbooleanfalseShades every second row.
TableBody borderedbooleanfalseRules between rows and cells.
TableBody interactivebooleanfalseHover ground for clickable rows.
TableRow selectedbooleanfalseBlue 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 labelstring—Sort button name when children aren’t text.
TableHead numericbooleanfalseRight-aligns the label.
TableCell numericbooleanfalseRight-aligns, with tabular figures.
TableCell tone'ok' | 'note' | 'act' | 'bad' | 'live' | 'idle'—Colours the value as a finding.
TableCell mutedbooleanfalseSecondary ink.
TableCheck checkedboolean—The ticked state.
TableCheck onToggle() => void—Runs on click, Enter or Space.
TableCheck labelstring—Accessible name, such as “Select Bore 12”.
TableCheck indeterminatebooleanfalseDash for a partial selection.

Tokens

TokenValueWhere it lands
--spacing-8 / -10 / -1232 / 40 / 48pxRow heights.
--spacing-312pxCell padding, left and right.
--font-size-md13pxCell values.
--font-size-sm12pxColumn headers.
--font-weight-medium475Headers and numbers.
--color-grey-100#F5F5F5Striped rows, header hover.
--color-grey-200#F1F1F1Row hover.
--color-primary-light#EBFAFFSelected row.
--color-primary-active#1877F2Selected bar, active caret, focus ring.
--global-neutral-grey-900#7F7D83Sort caret at rest.
--color-grey-300#ECECEDCell rules when bordered.
--color-border-default#E6E6E6Outer hairline and header rule.
--radius-lg8pxOuter corners.

Status indicator

Shows the condition of a device as a dot and a word.

Live React portVue source: WildStatusIndicator.vueStorybook story: Data Display/Status IndicatorReact component: WStatusIndicator, WStatusRow, WStatusDot

Try it

Change the status, then turn the pulse and label off.

Online
Status
Size
Pulse
Label
<WildStatusIndicator
  status="online"
  size="md"
/>

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.

Online
online
Warning
warning
Error
error
Offline
offline
Info
info
Success
success
<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.

Online
sm · 8
Online
md · 12
Online
lg · 16
<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.

<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.

KeyWhat it does
TabMoves focus to a row that has onClick.
Enter / SpaceOpens the row.

Props

PropTypeDefaultDescription
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.
labelstring—Replaces the default label.
pulsebooleantrueHalo on online, warning and error.
showLabelbooleantrueShows the word. Off, it becomes the accessible name.
childrenReactNode—Replaces the label text. The Vue default slot.

WStatusRow A device row: the dot, a name and a meta line.

PropTypeDefaultDescription
statusStatusKind—The condition, as above.
titlestring—The device name.
metastring—The status word and time since the last reading.
trailingReactNode—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.
firstbooleanfalseDrops the top divider. Set it on the first row.
pulsebooleanfalseHalo on the dot. Off in lists.

Tokens

TokenValueWhere it lands
--global-green-800#019939Online and success.
--global-squash-1000#9D6508Warning.
--global-red-800#E12121Error.
--global-blue-1000#1877F2Info, and the row focus ring.
--global-neutral-grey-900#7F7D83Offline.
--global-neutral-grey-1000#4F4D55Label.
--global-green-300#C1F4D4Online pulse halo.
--global-squash-300#FFE9BAWarning pulse halo.
--global-red-300#FFC9C9Error pulse halo.
--global-neutral-grey-1300#0A090BRow title.
--global-neutral-grey-1100#2D2B32Row meta line.
--global-neutral-grey-300#F1F1F1Row hover.
--global-neutral-grey-400#ECECEDRow press and divider.
--radius-full9999pxThe dot.

Activity feed

A day-grouped history of alarms, reports and integrations, with filters, collapsed repeats and held arrivals.

Refined portVue source: WildActivityFeed.vueStorybook story: Data Display/Activity FeedReact component: WActivityFeed, WActivityAction

Try it

Filter it, turn on Problems only, simulate an event, and move between rows with the arrow keys.

Activity
Problems only
Today7
  • Alarm SMS, failed:Low battery alarm SMS to Sam Okonkwo
    Bore 12
    The carrier rejected the number. Check the contact’s phone.
    12 minutes ago, Tue 29 Sep 2026, 11:28 am
  • Alarm email, succeeded:High pressure alarm emailed to 3 people, 3 times
    North Pump, 840ms, Pressure over 250 kPa
    25 minutes ago, Tue 29 Sep 2026, 11:15 am
  • Report, succeeded:Weekly irrigation report sent
    Sam Okonkwo, 12.4s, Attachment
    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.
    3 hours ago, Tue 29 Sep 2026, 8:40 am
  • Calculation, info:Evapotranspiration recalculated for 24 sites
    3.1s
    5 hours ago, Tue 29 Sep 2026, 6:40 am
Yesterday3
  • CSV export, succeeded:Soil moisture exported
    Mere Tane, 2.3s, 3 depths · 30 days
    yesterday, Mon 28 Sep 2026, 9:40 am
  • API call, failed:Reading sync from the Emu Park gateway
    Emu Park
    The gateway has been unreachable for 42 minutes.
    yesterday, Mon 28 Sep 2026, 7:40 am
  • Data import, succeeded:Imported 1,240 readings from CSV
    Sam Okonkwo, 5.4s
    yesterday, Mon 28 Sep 2026, 4:40 am
This week2
  • Alarm email, succeeded:Tank low alarm emailed to 2 people
    Tank sensor 2, 650ms, Level under 20%
    3 days ago, Sat 26 Sep 2026, 10:40 am
  • Report, pending:Monthly compliance report
    4 days ago, Fri 25 Sep 2026, 10:40 am
Older1
  • Webhook, succeeded:Readings pushed to Hortplus
    1.2s, hortplus.com/ingest
    20 Sep, Sun 20 Sep 2026, 10:40 am
New events wait behind a pill until you show them.
Density
State
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}
/>

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
Today7
  • Alarm SMS, failed:Low battery alarm SMS to Sam Okonkwo
    Bore 12
    The carrier rejected the number. Check the contact’s phone.
    12 minutes ago, Tue 29 Sep 2026, 11:28 am
  • Alarm email, succeeded:High pressure alarm emailed to 3 people, 3 times
    North Pump, 840ms, Pressure over 250 kPa
    25 minutes ago, Tue 29 Sep 2026, 11:15 am
  • Report, succeeded:Weekly irrigation report sent
    Sam Okonkwo, 12.4s, Attachment
    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.
    3 hours ago, Tue 29 Sep 2026, 8:40 am
  • Calculation, info:Evapotranspiration recalculated for 24 sites
    3.1s
    5 hours ago, Tue 29 Sep 2026, 6:40 am
Yesterday3
  • CSV export, succeeded:Soil moisture exported
    Mere Tane, 2.3s, 3 depths · 30 days
    yesterday, Mon 28 Sep 2026, 9:40 am
  • API call, failed:Reading sync from the Emu Park gateway
    Emu Park
    The gateway has been unreachable for 42 minutes.
    yesterday, Mon 28 Sep 2026, 7:40 am
  • Data import, succeeded:Imported 1,240 readings from CSV
    Sam Okonkwo, 5.4s
    yesterday, Mon 28 Sep 2026, 4:40 am
This week2
  • Alarm email, succeeded:Tank low alarm emailed to 2 people
    Tank sensor 2, 650ms, Level under 20%
    3 days ago, Sat 26 Sep 2026, 10:40 am
  • Report, pending:Monthly compliance report
    4 days ago, Fri 25 Sep 2026, 10:40 am
Older1
  • Webhook, succeeded:Readings pushed to Hortplus
    1.2s, hortplus.com/ingest
    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
loading
Activity

No activity yet

Alarm notifications, reports and integrations appear here as they run.
nothing yet
ActivityProblems only

No problems to show

Everything in this period ran as expected.
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.

KeyWhat it does
TabMoves to the chosen filter, Problems only, the new events pill, each row and its actions, then Show older activity.
Left / RightIn the filter, moves to the next or previous family and shows it.
SpaceTurns Problems only on or off.
Down / UpMoves to the next or previous row.
Home / EndMoves to the first or last row. In the filter, picks the first or last family.
Enter / SpaceOpens the focused row.
EscapeHides the time tooltip.

Props

PropTypeDefaultDescription
itemsActivityExecution[]—The events, in any order. The feed sorts them.
nowDate | number—One clock for every relative time.
timeZonestring—IANA zone for days and times.
density'comfortable' | 'compact''comfortable'Row height and type size.
titlestring'Activity'Toolbar title and the feed’s accessible name.
filter'all' | ProcessFamily'all'Which family to show.
onFilterChange(f) => void—Shows the family filter.
failuresOnlybooleanfalseShows failures and warnings only.
onFailuresOnlyChange(on) => void—Shows the Problems only checkbox.
collapseRepeatsbooleantrueJoins back-to-back identical events into one row.
newCountnumber0Held-back arrivals, shown as a pill.
onShowNew() => void—Releases the held-back arrivals.
loadingbooleanfalseSkeleton rows in place of the list.
onItemClick(item) => void—Makes each row a button.
renderActions(item) => ReactNode—Row actions. Use WActivityAction.
hasMorebooleanfalseShows the Show older activity button.
onLoadMore() => void—Loads the next page.
maxHeightnumber—Scrolls inside the feed, with sticky day labels.

Tokens

TokenValueWhere it lands
--global-neutral-grey-400#ECECEDRail and toolbar rule.
--global-neutral-grey-300#F1F1F1Rule after each day label.
--global-neutral-grey-1100#2D2B32Day label, and new events pill hover.
--global-neutral-grey-1000#4F4D55Day count.
--global-neutral-grey-200#F5F5F5Day count chip.
--global-neutral-grey-1300#0A090BToolbar title and new events pill.
--global-blue-100#EBFAFFWash behind an arriving row.
--global-blue-1000#1877F2Show older activity, and focus ring.
--radius-3xl16pxFeed corners.
--shadow-card0 2px 12px -1px …Feed elevation.

Filter chips

The removable chips that show what a list has been narrowed to, with the count that is left and a way to clear them all.

Refined portVue source: WildFilterChips.vueStorybook story: Data Display/Filter ChipsReact component: WFilterChips

Try it

Remove a filter and the count widens with it. Tab into the row, then use the arrow keys and Backspace.

Filters
12 of 120 sites
Size
Label
Count
Icons
Disabled
<WildFilterChips
  :chips="activeFilters"
  label="Filters"
  :count="countLabel"
  empty-text="No filters applied"
  @remove="removeFilter"
  @clear="clearFilters"
/>
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
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.

value
field and value
with icon
truncated
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.

sm · 28
md · 32
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.

rest
hover
pressed
focus
disabled
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
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.

Sites
Filters
43 of 120 sites
  • Bore 12Soil moisture 18 kPa · Kiwifruit · 64% battery
  • Emu ParkSoil moisture 55 kPa · Kiwifruit · 88% battery
  • Omokoroa Bore 10Soil moisture 38 kPa · Kiwifruit · 61% battery
  • Pongakawa Bore 17Soil 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.

KeyWhat it does
TabMoves into the row, onto the chip focused last, then out.
→ / ←Moves between chips and Clear all, wrapping at the ends.
Home / EndMoves to the first or last item.
Backspace / DeleteRemoves the focused chip.
Enter / SpaceRemoves the focused chip, or clears all from Clear all.

Props

PropTypeDefaultDescription
chips{ id, label, field?, icon? }[]—The filters, in the order they were added.
size'sm' | 'md' | 'lg''sm'28, 32 or 36px tall.
labelstring—A leading label, which also names the row.
countstring—The result count, announced when it changes.
clear-labelstring'Clear all'Shown from two chips up.
empty-textstring—Shown when there are no chips.
aria-labelstring'Active filters'The row’s name with no visible label.
disabledbooleanfalseLocks the chips and Clear all.
@remove(id: string) => void—A chip’s ×, Backspace or Delete.
@clear() => void—Clear all.

Tokens

TokenValueWhere it lands
--global-blue-100#EBFAFFChip ground.
--global-blue-200#D6F4FFThe × circle on hover.
--global-blue-300#BDEDFFChip edge; the × circle pressed.
--global-blue-400#A3E7FFChip edge while the × is hovered.
--global-blue-1100#0C296AChip text; the × on hover.
--global-blue-1000#1877F2The × at rest; Clear all; focus.
--global-neutral-grey-1000#4F4D55Count and empty text.
(no token)24pxChip radius.

Activity item

One row of an activity feed: what ran, how it went, who started it, where and when.

Refined portVue source: WildActivityItem.vueStorybook story: Data Display/Activity ItemReact component: WActivityItem

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
    14 minutes ago, Mon 28 Sep 2026, 9:27 am
Click the row, or tab to it and press Enter.
Status
Process
Density
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} />
    </>
  }
/>

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
    Sam Okonkwo, North Pump, 5.0s, hooks.farmops.nz/wildeye
    The endpoint returned 503 after 3 attempts
    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.

Alarms
Alarm email
Alarm SMS
Reports and exports
Report
CSV export
Integrations
API call
Webhook
Data import
Calculations
Calculation
No family is red, green or amber, because those colours mean status.

Statuses

Five, each with its own mark shape.

success
failure
warning
pending
info
  • Alarm email, succeeded:High soil moisture alarm
    Emu Park, 1.2s, sam.okonkwo@emupark.co.nz
    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
    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
    41 minutes ago, Mon 28 Sep 2026, 9:00 am
  • Report, pending:Monthly rainfall report
    Emu Park, rainfall-september.pdf
    in 20 minutes, Mon 28 Sep 2026, 10:01 am
  • Data import, info:Logger firmware notes imported
    Priya Raman, release-4.2.txt
    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
    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
    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
    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
    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
    41 minutes ago, Mon 28 Sep 2026, 9:00 am
plain row, hover
Actions sit beside the row’s button, never inside it.

Metadata

Everything after the description is optional.

  • Report, warning:Weekly irrigation report
    Priya Raman, Emu Park, 18.4s, Attachment, irrigation-weekly.pdf
    Sent without rainfall; Weather station 4 has no readings since Friday
    2 hours ago, Mon 28 Sep 2026, 7:09 am
everything
  • Report, warning:Weekly irrigation report
    Priya Raman, 18.4s, Attachment
    Sent without rainfall; Weather station 4 has no readings since Friday
    2 hours ago, Mon 28 Sep 2026, 7:09 am
no site or target
  • Calculation, succeeded:Soil water deficit recalculated for 14 sites
    9 minutes ago, Mon 28 Sep 2026, 9:32 am
description only
  • API call, succeeded:Reseller readings sync, 4 times
    Bore 12, 640ms, api.agrilink.co.nz/v2/readings
    38 minutes ago, Mon 28 Sep 2026, 9:03 am
count

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
    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
    14 minutes ago, Mon 28 Sep 2026, 9:27 am
  • API call, succeeded:Reseller readings sync, 4 times
    Bore 12, 640ms, api.agrilink.co.nz/v2/readings
    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
    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
    14 minutes ago, Mon 28 Sep 2026, 9:27 am
  • API call, succeeded:Reseller readings sync, 4 times
    Bore 12, 640ms, api.agrilink.co.nz/v2/readings
    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
    Emu Park
    just now, Mon 28 Sep 2026, 9:40 am
  • Webhook, succeeded:Alarm webhook
    North Pump
    4 minutes ago, Mon 28 Sep 2026, 9:37 am
  • API call, succeeded:Reseller readings sync
    Bore 12
    38 minutes ago, Mon 28 Sep 2026, 9:03 am
  • Calculation, succeeded:Soil water deficit
    Emu Park
    5 hours ago, Mon 28 Sep 2026, 4:31 am
  • Report, succeeded:Weekly irrigation report
    Emu Park
    yesterday, Sun 27 Sep 2026, 1:41 pm
  • CSV export, succeeded:Season export
    Emu Park
    3 days ago, Fri 25 Sep 2026, 8:11 am
  • Data import, succeeded:Rain gauge history import
    Emu Park
    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.

Recent activity

Emu Park and North Pump · last 24 hours
1 failed1 warning

Today

  • CSV export, pending:Soil moisture readings export
    Sam Okonkwo, Emu Park, emu-park-soil-sep.csv
    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
    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
    14 minutes ago, Mon 28 Sep 2026, 9:27 am
  • API call, succeeded:Reseller readings sync, 4 times
    Bore 12, 640ms, api.agrilink.co.nz/v2/readings
    38 minutes ago, Mon 28 Sep 2026, 9:03 am
  • Report, warning:Weekly irrigation report
    Emu Park, 18.4s, Attachment, irrigation-weekly.pdf
    Sent without rainfall; Weather station 4 has no readings since Friday
    2 hours ago, Mon 28 Sep 2026, 7:09 am

Yesterday

  • Calculation, succeeded:Soil water deficit
    Emu Park, 220ms, Deficit (mm)
    Sun 27 Sep 2026, 4:41 pm
  • Data import, info:Rain gauge history import
    Priya Raman, Emu Park, 9.8s, rainfall-2025.xml
    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.

KeyWhat it does
TabMoves to a clickable row, then into its actions.
Shift + TabMoves back.
Enter / SpaceOpens the row, or runs the focused action.
EscapeHides the time or action tooltip.

Props

PropTypeDefaultDescription
item{ id, type, status, description, timestamp, … }—The run. Also: target, siteName, duration, errorMessage, actor, count.
item.typestring—One of the eight process types.
item.statusstring—One of the five statuses.
showSitebooleantrueShows the site.
showTargetbooleantrueShows the recipient, file or URL.
density'comfortable' | 'compact''comfortable'Compact: 24px tile, one line each.
nowDate | numbermount timeClock relative times count from. Share one.
timeZonestringbrowserFor calendar days and exact times.
timeFormat'relative' | 'clock''relative'Clock time, under a day heading.
onClick(item) => void—Makes the whole row a button.
actionsReactNode—WActivityAction buttons.
revealActions'hover' | 'always''hover'Always keeps them showing. Touch screens always show them.
as'div' | 'li''div'li inside a list.
WActivityAction labelstring—Required. Accessible name and tooltip.
WActivityAction iconReactNode—16px Heroicons glyph.
WActivityAction disabledbooleanfalseGreys out; stops the click.

Tokens

TokenValueWhere it lands
--global-neutral-grey-200#F5F5F5Hover ground.
--global-neutral-grey-300#F1F1F1Pressed ground.
--global-neutral-grey-400#ECECEDInitials disc.
--global-neutral-grey-500#E6E6E6Tile outline, row rules.
--global-neutral-grey-800#ADACB0Meta dots.
--global-neutral-grey-900#7F7D83Info mark, paperclip.
--global-neutral-grey-1000#4F4D55Meta, time, initials.
--global-neutral-grey-1300#0A090BDescription.
--global-blue-1000#1877F2Focus, pending mark, output glyphs.
--global-green-800#019939Success mark.
--global-red-800#E12121Failure mark, edge.
--global-red-1000#961616Failure message.
--global-squash-800#EC980CWarning edge.
--global-squash-900#C47E0AWarning mark.
--global-squash-1100#764C06Warning message.
--global-violet-800#6E23C9Alarm glyphs.
--global-teal-700#0692BCIntegration glyphs.
--global-purple-900#7B238DCalculation glyphs.