Tells someone about something that needs their attention, in one of five tones.
Live React portVue source: WildAlert.vueStorybook story: Feedback/AlertReact component: WAlert
Try it
Switch the variant, then press the cross or tab to it.
Battery below 20%Logger 4412 will stop reporting in about six days.
Variant
Size
Title
Dismissible
<WildAlert
variant="warning"
size="md"
title="Battery below 20%"
dismissible
@dismiss="hide"
>
Logger 4412 will stop reporting in about six days.
</WildAlert>
When to use
Use an alert for a condition that affects the page or panel it sits in.
Use one per region, at the top of what it describes.
Let people dismiss it only once the message has done its job.
Don’t use it
To confirm an action that just finished. Use a toast.
When there’s nothing to show yet. Use an empty state.
Variants
Five tones. Pick by meaning: warning and error ask for action; info, success and neutral only inform.
New schedule for logger 4412Readings now arrive every fifteen minutes.
Logger 4412 is reportingIts readings will appear here within fifteen minutes.
Battery below 20%Logger 4412 will stop reporting in about six days.
Sync failedThree sites didn’t report in the last window. Try again in a few minutes.
Maintenance on SundayReadings may arrive late between 2am and 4am.
<WildAlert variant="warning" title="Battery below 20%">
Logger 4412 will stop reporting in about six days.
</WildAlert>
Readings arrive every fifteen minutes.
<WildAlert variant="info">Readings arrive every fifteen minutes.</WildAlert>
Drop the title in dense screens, where one line of consequence is enough.
Sizes
Three, with 13, 14 and 15px text. The icon and the cross scale with the padding.
Size smReadings arrive every fifteen minutes.
Size mdReadings arrive every fifteen minutes.
Size lgReadings arrive every fifteen minutes.
Use sm inside forms and panels, md for page banners and lg for a page with little else on it.
Content
Put the fact in the title: “Battery below 20%”.
Put the consequence or the next step in the body: “Logger 4412 will stop reporting in about six days.”
Keep the title to six words or fewer, in sentence case, with no full stop.
Name the logger or site, so nobody has to guess which one.
Accessibility
Error and warning are announced as they appear; the other tones wait their turn. The cross is named “Dismiss” plus the title, so a stack of alerts stays distinguishable.
Key
What it does
Tab
Moves focus to the cross, when the alert is dismissible.
Enter / Space
Dismisses the alert and moves focus to the next control.
A long message wraps onto the next line, so none of it is cut off. The last toast closes itself after three seconds.
Content
Say what happened in a few words: “Threshold saved”.
Name the logger or site when it matters which one.
Use sentence case, with no full stop.
Keep it short enough to read at a glance.
Don’t use it
Auto-closing an error. People need time to read it.
Stacking more than three at once.
Accessibility
Error and warning toasts use role="alert", which interrupts a screen reader, and the others use role="status". Set role to override this. Auto-close pauses while the pointer is over the toast or focus is inside it.
Key
What it does
Tab
Moves focus to the cross, announced as “Close”.
Enter / Space
Closes the toast and moves focus to the next control.
A row that loads into an avatar, two lines and a badge gets a skeleton for each.
<WildSkeleton variant="text" count={3} />
Three lines hold exactly three lines of body text, so the paragraph lands without a shift.
Accessibility
A skeleton is hidden from screen readers and takes no focus. Set aria-busy on the region that’s loading, and clear it when the content lands. The pulse stops under reduced motion.
Props
Prop
Type
Default
Description
variant
'text' | 'circular' | 'rectangular' | 'rounded'
'text'
A line, a circle, a square block or a rounded block.
width
number | string
—
Defaults to 100%, or 40px for circular.
height
number | string
—
Defaults to 16, 40 or 100px by variant.
radius
number | string
—
Overrides the variant corner.
count
number
1
Repeats the skeleton in a column.
animation
'pulse' | 'none'
'pulse'
Pulse, or a still block.
style
CSSProperties
—
Extra inline styles, applied last.
Tokens
Token
Value
Where it lands
--global-neutral-grey-300
#F1F1F1
The fill.
--radius-sm
4px
Text corners.
--radius-lg
8px
Rounded corners, by default.
--radius-none
0
Rectangular corners.
--radius-full
9999px
Circular corners.
(no token)
1500ms
One pulse cycle.
Empty state
What an empty container says, decided by why it is empty: nothing yet, no matches, or all clear.
Change the reason, and the picture, words and action change with it. Remove a filter, or clear them all.
No sites match these filters
42 sites are hidden. Remove a filter to widen the list.
Region: Bay of PlentyStatus: OfflinePeriod: Last 7 days
Reason
Size
Description
Actions
<WildEmptyState
reason="no-matches"
title="No sites match these filters"
description="42 sites are hidden. Remove a filter to widen the list."
:filters="activeFilters"
@remove-filter="removeFilter"
primary-label="Add a site"
@primary="create"
secondary-label="Clear filters"
@secondary="recover"
/>
Only nothing-yet gets a filled button, because creating the first record is its way out. The other two step down to grey.
Three reasons
Pick the reason by why the list is empty. Each has its own picture, words and weight of action.
No sites yet
Add your first logger and its readings appear here within a few minutes.
nothing yet
No sites match these filters
42 sites are hidden. Remove a filter to widen the list.
Region: Bay of PlentyStatus: Offline
no matches
No alarms this week
Every site reported inside its thresholds.Checked 2 minutes ago
all clear
The picture is a ghost of what belongs there: skeleton cards, a list under a lens, or a tick when empty is good news.
Sizes
A whole page, the body of a card, or a row in a table.
Connect your first logger
Plug in a WildEye logger, press its button, and it appears here with its first reading.
Use page size when the whole screen has nothing yet. It has a larger picture, title and buttons.
AlarmsThis week
No alarms this week
Every site reported inside its thresholds.Checked 2 minutes ago
Panel size fills a card’s body. The card keeps its header, so people still know what would be here.
Loggers0 of 42
NameLast readingBattery
No loggers match “bore 14”
Check the spelling, or search by serial number.
Inline size sits where the rows would be, so the table keeps its header and shape.
Content
Three parts, each with one job.
Part
Its job
Example
Title
Say what is empty, in the reader’s words.
No sites match these filters
Description
Say why, or what to do next. Never repeat the title.
42 sites are hidden. Remove a filter to widen the list.
Action
One verb and a noun. The way out, not a tour.
Clear filters
Not an error
A list that loaded and came back empty is an empty state. A list that failed to load is an alert.
Couldn’t load sitesThe readings service did not respond. Try again, or check the status page.
the load failed — alert
No sites yet
Add your first logger to start seeing readings.
the load worked and returned nothing — empty state
Empty says there’s nothing here. An error says the product couldn’t tell you.
Filters
On no-matches, show the filters that emptied the list as filter chips. Each × removes one, and its circle fills on hover.
Name how many results the filters hide, so people know removing one is worth it.
Offer Clear filters as the secondary action.
Props
Prop
Type
Default
Description
reason
'nothing-yet' | 'no-matches' | 'all-clear'
'nothing-yet'
Why it’s empty. Sets the picture and the action’s weight.
size
'page' | 'panel' | 'inline'
'panel'
A whole screen, a card body, or a table row.
title
string
—
What is empty. Required.
description
string
—
Why, or what to do next.
primary-label
string
—
The way out. Filled only on nothing-yet.
secondary-label
string
—
A second, quieter action.
filters
{ id, label }[]
—
no-matches: the filters that emptied the list.
checked-text
string
—
all-clear: a live line such as “Checked 2 minutes ago”.
Tokens
Token
Value
Where it lands
--color-grey-1300
#0A090B
Title.
--color-grey-1000
#4F4D55
Description.
--color-grey-400 / 500
#ECECED / #E6E6E6
Skeleton bars in the picture.
--shadow-group
0 2px 12px -1px …
The ghost cards.
--color-primary
#00AEEF
The “+” tile on nothing-yet.
TONE.ok
#E1FAEA · #C1F4D4 · #019939
Rings and tick on all-clear.
--radius-field
10px
Ghost cards and the “+” tile.
Dialog
Asks for focused input or a decision in a panel over the page.
Live React portVue source: WildDialog.vueStorybook story: Feedback/DialogReact component: WDialog
Try it
Open it, change its width, then close it with Escape, the scrim or a button. Focus goes back to Edit site.
A black line marks the point the reading is judged against. Pass fillBelow so the bar changes colour under the line. Keep the line clear of an end readout, which sits over it.
Title the drawer with the name of the thing it shows: the site, logger or alarm.
Put the primary action last in the footer, and start it with a verb.
Keep the body to one task. A second task needs its own drawer or page.
Accessibility
The drawer is announced as a dialog named by its title. Focus moves into it on open, stays inside it, and returns to the control that opened it.
Key
What it does
Tab / Shift+Tab
Moves through the drawer’s controls, wrapping at either end.
Escape
Closes the drawer. An open list inside it closes first.
Enter / Space
On the close button, closes the drawer.
Props
Prop
Type
Default
Description
open
boolean
—
Shows the drawer. Required.
title
string
—
Heading in the header, and the accessible name.
size
'sm' | 'md' | 'lg' | 'xl' | 'full'
'md'
320, 400, 560, 720px or full; full at 640px and under.
position
'left' | 'right'
'right'
The edge it slides in from.
showClose
boolean
true
Shows the close button in the header.
closeOnOverlay
boolean
true
Closes when the scrim is clicked.
closeOnEsc
boolean
true
Closes when Escape is pressed.
ariaLabel
string
—
Accessible name when there is no title.
@close
event
—
Fired when the drawer asks to close.
#header
slot
—
Replaces the title. The close button stays.
#footer
slot
—
Actions, aligned right.
Tokens
Token
Value
Where it lands
--dialog-overlay
rgba(10, 9, 11, 0.5)
Scrim, shared with the dialog, 2px blur.
(no token)
0 20px 25px -5px …
The dialog’s panel shadow.
--global-neutral-grey-500
#E6E6E6
Leading edge, header and footer rules.
--global-neutral-grey-1200
#1D1C20
Title, 18px at 600.
--duration-slow
300ms
Slide in, with --ease-out.
--duration-moderate
200ms
Slide out, with --ease-in.
(no token)
1000
Z-index, above toolbars.
Confirm dialog
Asks one question before an action that can't be undone, such as deleting an API key.
Live React portVue source: WildConfirmDialog.vueReact component: WConfirmDialog
Try it
Set the label to OK to see how little the button says. Press Delete key to reopen it.
Delete this API key?
It is removed permanently.
Confirm label
Tone
Consequence
<WConfirmDialog
open={open}
title="Delete this API key?"
confirmLabel="Delete key"
onConfirm={deleteKey}
onCancel={() => setOpen(false)}
>
It is removed permanently.
</WConfirmDialog>
When to use
Use it before an action that deletes data or can’t be undone.
Use it when an action affects more than the thing in front of the person, such as anything that uses an API key.
Use it, in the primary tone, before a reversible action with wide effects, such as pausing an automation.
Don’t use it
For routine actions that are quick to undo. Run them, and confirm with a toast.
Destructive is the default. Use primary when nothing is lost but the effect is wide, such as pausing an automation. Add a consequence panel when the action reaches beyond the thing itself.
Cancel this invite?
It is removed permanently.
<WConfirmDialog open={open} title="Cancel this invite?" confirmLabel="Cancel invite" cancelLabel="Keep invite" onConfirm={cancelInvite} onCancel={() => setOpen(false)}
>
It is removed permanently.
</WConfirmDialog>
The confirm sits on the right, where the primary always sits. Cancel can name what it keeps.
Delete this API key?
It is removed permanently.
Anything using this key can no longer reach the API.
<WConfirmDialog open={open} title="Delete this API key?" confirmLabel="Delete key" consequence="Anything using this key can no longer reach the API." onConfirm={deleteKey} onCancel={() => setOpen(false)}
>
It is removed permanently.
</WConfirmDialog>
Pause this automation?
It stops running until you turn it back on.
<WConfirmDialog open={open} title="Pause this automation?" confirmLabel="Pause automation" tone="primary" onConfirm={pauseAutomation} onCancel={() => setOpen(false)}
>
It stops running until you turn it back on.
</WConfirmDialog>
Content
Ask the question in the title, naming the thing: “Delete this API key?”
Say in the body what is lost and what isn’t.
Name the consequence on the button: “Delete key”, “Cancel invite”. People who skim the body still read the button.
Name what else breaks in the consequence panel, with a number where there is one.
Name what Cancel keeps when “Cancel” would be ambiguous: “Keep invite”.
Don’t use it
OK, Yes or Confirm on the button. They don’t say what happens.
Accessibility
It opens as an alert dialog, named by its title and described by its body and consequence. Focus lands on Cancel, so a double Enter can’t delete anything, and returns to the trigger on close.
Key
What it does
Tab / Shift+Tab
Moves between the buttons, staying inside the dialog.
Enter / Space
Presses the focused button.
Esc
Cancels and closes.
Props
Prop
Type
Default
Description
open
boolean
—
Shows the dialog.
title
ReactNode
—
The question, naming the thing affected.
children
ReactNode
—
What is lost, in a sentence or two.
confirmLabel
string
—
Verb and object: Delete key. Required.
cancelLabel
string
'Cancel'
The safe action.
tone
'destructive' | 'primary'
'destructive'
Primary when nothing is lost, such as a pause.
consequence
ReactNode
—
Warning panel for knock-on effects.
loading
boolean
false
Spinner on the confirm while the action runs.
onConfirm
() => void
—
Runs the action.
onCancel
() => void
—
Cancel, Esc, the close button or the scrim.
closeOnOverlay
boolean
true
Whether a scrim click cancels.
contained
boolean
false
Fills the nearest positioned parent instead of the viewport.
autoFocus
boolean
true
Moves focus to Cancel on open.
Tokens
Token
Value
Where it lands
max-w-sm
384px
Panel width.
--radius-3xl
16px
Panel corners.
--dialog-overlay
rgba(10, 9, 11, 0.5)
The scrim, with a 2px blur.
--color-error
#E12121
Destructive confirm fill.
Error message
The one-line failure text under a field, which holds its space so a message never shifts the layout.
An exclamation mark and a sentence, left-aligned under the field it belongs to.
Use a value between 0 and 250 kPa.
Length
One line is the target. A longer message wraps under its own text, keeps the mark on the first line, and pushes the next field down.
Enter a site name.
one line
Use a value between 0 and 250 kPa. Use numbers only, e.g. 120 or 120.5, with no unit after them.
wrapping
Inline, nothing below
Required.
no reserved line
Content
Say what to do, not what went wrong. “Use a value between 0 and 250 kPa” fixes the field; “Invalid value” only tells you it’s broken.
Use a value between 0 and 250 kPa.
do
Invalid value.
don't
Accessibility
Give the line an id and point the field’s aria-describedby at it, so a showing message is read with the field. In React the line also stays mounted as a polite live region, so a new message is announced without moving focus. The shipped Vue line is not a live region and unmounts when empty.
Change the tone and the copy changes with it. Press Replay to watch it arrive.
3 invites sent
We’ve emailed each of them a link to join Emu Park Orchard.
aroha.walker@emupark.co.nzOperator
sam.te-ao@emupark.co.nzViewer
priya.nair@baycitrus.co.nzViewer
Tone
Size
Items
Actions
Animate
<WildSuccessScreen
headline="3 invites sent"
subtitle="We’ve emailed each of them a link to join Emu Park Orchard."
:items="invites"
items-label="Invited people"
:primary="{ label: 'Done', onClick: close }"
:secondary="{ label: 'Invite more', onClick: next }"
/>
When to use
Use it to end a flow that changed something.
Use it in place of the form it confirms.
Don’t use it
When the person carries on where they were. Use a toast.
No green, amber or red in the syntax, because those mean state.
Languages
JSON is re-indented. XML and plain text appear as they arrive.
Weekly exportxml
<?xmlversion="1.0"encoding="UTF-8"?><exportsite="Emu Park"from="2026-09-01"to="2026-09-07"><!-- Daily mean soil moisture, 30 cm probe --><loggerserial="WE-10442"name="Bore 12"><readingdate="2026-09-01"unit="%">32.1</reading><readingdate="2026-09-02"unit="%">31.8</reading><readingdate="2026-09-03"unit="%">30.9</reading><readingdate="2026-09-04"unit="%">34.6</reading><readingdate="2026-09-05"unit="%">33.9</reading><readingdate="2026-09-06"unit="%">33.2</reading><readingdate="2026-09-07"unit="%">32.4</reading></logger></export>
Sync logplain text
05:30:02 Bore 12 Connected over LTE-M, signal -87 dBm05:30:04 Bore 12 Uploaded 96 readings at a 15-minute interval05:30:05 Bore 12 Battery 3.62 V, charging from solar05:31:10 Tank 3 No response, retry 1 of 3 in 60 s05:32:10 Tank 3 Connected over LTE-M, signal -101 dBm05:32:13 Tank 3 Uploaded 12 readings05:32:14 North Pump Alarm “Tank low” raised: 17.8 % is under the 20 % threshold05:32:15 North Pump Webhook POST https://hooks.example.com/wildeye/alarms returned 422 Unprocessable Entity after 1.2 s
Plain text is never highlighted, so no log line outranks the next.
Single line
For one value someone will paste elsewhere.
Endpoint
https://hooks.example.com/wildeye/alarms
API key
••••••••••••7c2e hidden, ending 7c2e
Masked shows any prefix and the last four characters. Copy takes the whole value.
05:30:02 Bore 12 Connected over LTE-M, signal -87 dBm05:30:04 Bore 12 Uploaded 96 readings at a 15-minute interval05:30:05 Bore 12 Battery 3.62 V, charging from solar05:31:10 Tank 3 No response, retry 1 of 3 in 60 s05:32:10 Tank 3 Connected over LTE-M, signal -101 dBm05:32:13 Tank 3 Uploaded 12 readings05:32:14 North Pump Alarm “Tank low” raised: 17.8 % is under the 20 % threshold05:32:15 North Pump Webhook POST https://hooks.example.com/wildeye/alarms returned 422 Unprocessable Entity after 1.2 s
wrapped
Sync logplain text
05:30:02 Bore 12 Connected over LTE-M, signal -87 dBm05:30:04 Bore 12 Uploaded 96 readings at a 15-minute interval05:30:05 Bore 12 Battery 3.62 V, charging from solar05:31:10 Tank 3 No response, retry 1 of 3 in 60 s05:32:10 Tank 3 Connected over LTE-M, signal -101 dBm05:32:13 Tank 3 Uploaded 12 readings05:32:14 North Pump Alarm “Tank low” raised: 17.8 % is under the 20 % threshold05:32:15 North Pump Webhook POST https://hooks.example.com/wildeye/alarms returned 422 Unprocessable Entity after 1.2 s
scrolling
Wrapped lines continue two characters in. Unwrapped, line numbers stay pinned.
States
Responsejson
No response yetThe body appears here after the first delivery.
Move the level: the figure changes at once and the fill catches up.
Tank 2 · North Pump62%
Pump running · full in about 42 min.
Level
Size
Tone
Value
Layout
Striped
Indeterminate
<WildProgressBar
label="Tank 2 · North Pump"
:value="12400"
:max="20000"
unit="L"
show-value="percent"
size="md"
help="Pump running · full in about 42 min."
/>
On auto, the tank turns to warning under 40% and error under 20%.
When to use
Use it for a job with a known end, such as an upload.
Use it inline, on auto tone, for a reading in a list.
Don’t use it
For one reading against a marked threshold line. Use Progress.
Label, value, track and fill, and a help line. Only the track is required.
Backfilling readings · Bore 12642 of 960 readings
Catching up after 10 days offline.
Soil 30 cm
38%
Soil 60 cm
54%
Soil 90 cm
100%
Inline gives the value at least 52px, so a column of percentages ends on one edge.
Sizes
Four heights. Label, value and help stay 12px at every size.
xs · 4px
File rows
sm · 6px
Table cells
md · 8px
With a label
lg · 12px
A card’s one reading
Tones
Tone says what the work is doing.
Syncing readings · Emu Park48%
Backfill complete · Bore 12100%
Uploading logs · North Pump57%
Signal is weak. It will finish, just more slowly.
Firmware upload · Bore 1271%
Connection lost at 71%. Try again when Bore 12 is back online.
Paused · Tank sensor 223%
Resumes when the logger next checks in.
Give warning and neutral a help line saying what’s wrong.
Auto tone
For readings. Two thresholds, as percentages of the range, set the colour.
Bore 12
92%
North Pump
36%
Weather stn 4
14%
20%40%
battery · warning 40, error 20
Readings
34%
Logs
81%
Exports
96%
70%90%
storage · warning 70, error 90
Error below warning means higher is better. Error above warning means higher is worse.
Value
Percent, fraction or your own words. A screen reader hears the same words.
Firmware62%
percent
Firmware1,270 of 2,048 KB
fraction
FirmwareAbout 2 s left
value-text
Import1%
0.4 of 100
Import99%
99.6 of 100
Import100%
100 of 100
It shows 99% until the end, and at least 1% once started.
States
Queued, moving, waiting and done.
tank-sensor-2.csv0%
Starts after bore-12.csv.
queued
Importing 1,240 readings44%
About a minute left.
striped
Waiting for Bore 12
Starts when Bore 12 next checks in.
indeterminate
Import complete100%
1,240 readings added to Bore 12.
complete
Stripes drift while a job runs. Under reduced motion, indeterminate holds still as a hatch.
In context
Firmware 4.2.1
Bore 12 · Emu Park
Update available
Ready to send0 of 2,048 KB
Bore 12 is on 4.1.8.
The badge is a polite live region, so a screen reader hears only the phase changes.
Battery
4 loggers · sorted by site
Bore 12
Emu Park · 4 min ago
92%
North Pump
Emu Park · 11 min ago
36%
Weather station 4
Kinross · 2 h ago
14%
Tank sensor 2
Kinross · 6 min ago
71%
Storage
Storage used6.1 of 8 GB
Warning from 70%, error from 90%.
Content
Name the work and its subject: Sending to Bore 12.
Use help for what happens next, such as time left.
Give an error its cause and the fix.
Accessibility
A screen reader hears the label and the value in words, even when the value is hidden. Indeterminate has no value. An error is announced when it appears.