Components/Feedback
EmptyState
Empty states explain why a place that would normally hold content is empty, and what the reader can do about it. They turn a blank region into an answer, whether the cause is a search that matched nothing, a feature not set up yet, or work that has finished and left nothing behind.
import { EmptyState } from '@growth-protocol-ai/alpha';No containers yet
Containers hold the assets a workflow reads. Add one and it shows up here.
When to use
Reach for an empty state wherever a region can legitimately hold nothing — a list before its first entry, a search that matched nothing, an area waiting on a file. Say why it is empty, and where the reader can do something about it give them exactly one way to start. A blank region with no explanation reads as a screen that broke rather than one that is ready.
When not to use
Do not use one to report a failure: a request that did not come back is ErrorView’s, and it offers a retry where this offers a first step. Do not show one while the answer is still on its way either — that is Skeleton or Spinner, and an empty state during a load tells the reader there is nothing when there may yet be.
type4 values
The type is what the emptiness means. One of the four paints something of its own at the calm register these specimens use: placeholder’s border, which says an area is reserved rather than open. Confirmation moves the signal colour onto its title only in Expressive and only with no icon to put it on, which the register axis below is where to see. The other two are the same box, and what makes each read as itself is which optional parts you fill in: a glyph, a description, an action, a hint. Naming all four is still the point, because “no content yet” and “nothing went wrong, you are done” are opposite messages in identical boxes, and data-type is what tells the four apart — but note that ErrorView fixes it to placeholder, so pair it with :not([data-module="error-view"]) when a rule means coming-soon.
You are all set
Your analysis lands in your inbox shortly.
No portfolios yet
A portfolio groups the trends you want to track together.
Drop a file to import
CSV or XLSX, and the first row is read as the header.
Workflows will come here
Reusable processes that govern how a metric is monitored.
| type | When to use |
|---|---|
confirmation | The operation finished and there is nothing left to do. It takes no action, because offering one undoes the message — the box is empty because the work is over. |
vacant | The container is empty and the reader can fill it. The default, and the one type where the emptiness is a task rather than a report. |
upload | Awaiting a file. Pair it with droppable where a drop handler is wired, and give the action the file input — the component draws the zone and never touches the file. |
placeholder | The feature is not built yet. A solid border keeps the reserved area visible while it holds nothing, which is what separates an area someone is still building from one the reader is invited to fill. |
register2 values
How loudly the box is allowed to say it, and it runs across every type. Calm is the default and the one to build in. Expressive moves the brand colour into whichever element is carrying the signal — and into exactly one of them, which is the discipline that keeps it from becoming decoration. The specimens below carry no action, because that part is yours to pass and is where the two registers are told apart by hand.
No containers yet
Containers hold the assets a workflow reads.
No containers yet
Containers hold the assets a workflow reads.
| register | When to use |
|---|---|
calm | The default, and the baseline to get right first: a --text-typical glyph, --border-default edges and a --surface-depth-1 fill. The restraint almost every product surface wants. |
expressive | The brand layer. The glyph goes --button-primary, a filled box swaps its tonal fill for --aquarelle-graphics-blue, and a dashed or solid border promotes to match. A confirmation with no glyph puts the colour on its title instead, and gives it straight back the moment a glyph arrives. |
Nothing to show yet
No containers yet
Containers hold the assets a workflow reads.
Drop a file to import
CSV or XLSX, and the first row is read as the header.
No portfolios match that search
Try a shorter term, or clear the filters to see everything again.
| State | When to use |
|---|---|
Title only | Everything but the title is optional, and this is what is left when you pass nothing else. The resting box carries a transparent border rather than none, so a dashed or a solid one arriving later does not shift the copy by a pixel. |
On its own surface | filled tints the box with --surface-depth-1, for an empty state that has to read as a surface rather than as a hole in one — a bare page, or a region with no chrome of its own. Leave it off inside a card, which already draws one. |
A wired drop target | An upload zone with droppable set: the transparent border becomes a dashed --border-default, and in Expressive it promotes to the brand colour alongside the glyph. What sits beside it is still yours — the component draws the zone and never touches a file. |
Filling its region | fill stretches the box against a flex-column parent and centres it on both axes, so it takes the space actually left over instead of a height someone guessed at. This is the shape most full-page empty states want. |
The title is a real heading, so choose its element for the outline rather than for its size: h1 where the empty state is the page, p inside a card that already has a heading of its own. An h3 sitting under nothing is the outline titleAs exists to avoid.
The glyph is decorative and hidden from assistive technology, so nothing may live only in it. What is heard is the title, the description and the labels below them — and the hint is a link, so its text has to name where it goes rather than say “learn more”.
Keep the copy short. The description is held to a readable measure and every line is centred, which is right for a sentence or two and wrong for a paragraph the reader has to work through in order.
Pass a glyph only where it adds something the title does not. Which one belongs is a content decision — the type does not imply one — and a generic mark above a clear sentence is furniture.
droppable is a promise the component cannot keep on its own: it draws a drop target, so pass it only where a drop handler is wired. An upload zone with nothing listening uses its action alone and keeps its edges clean.
Reach for placeholder for any coming-soon surface — an unbuilt stage, tab, view or preview. Do not write that promise into a vacant one instead: vacant says the reader can fill the container now, and a feature that does not exist yet cannot be filled.
A placeholder keeps the ghost action even in Expressive. A solid brand button on a feature nobody can use yet is a loud invitation to do nothing, so the softer signal there is deliberate rather than an oversight.
A confirmation takes no action at all. Offering one contradicts the message, and the reader is left working out which of the two to believe.
One action. A second, competing button belongs on the surface around the empty state — a box whose whole message is that there is nothing here cannot also be a decision.
When the empty state is the whole content of its region, pass fill and make the parent a flex column. Never a viewport height and never auto margins: a viewport height cannot know how tall the header and the filter bar above it are, so it clips or overflows, and auto margins centre a box that is still content-sized. Both look right in a screenshot and are wrong the moment the chrome changes.
The row of accepted files below an upload zone is yours to draw; the package ships no component for it. Match the zone so the two read as one control — the same corner radius, and a solid border at the weight the dashed one uses.
Matching the action to the register
The action is the one part the component cannot repaint, because you pass it: calm takes a ghost Button, Expressive takes the default solid. Calm above, Expressive below. An expressive box with a ghost action is the mismatch to look for — nothing fails, it simply reads as two decisions made separately.
No containers yet
Containers hold the assets a workflow reads.
No containers yet
Containers hold the assets a workflow reads.
| Owner | Prop | Type | Default | Notes |
|---|---|---|---|---|
EmptyState | type | "confirmation" | "vacant" | "upload" | "placeholder" | "vacant" | What the emptiness means, and it reaches the DOM as data-type so a consumer or a test can tell two identical boxes apart. |
EmptyState | register | "calm" | "expressive" | "calm" | Calm or the brand layer. It repaints the box and the glyph; the action stays yours to match. |
EmptyState | title | string | — | Required, and it displaces the div’s own title attribute — this is copy the reader sees, not a tooltip. |
EmptyState | description | ReactNode | — | The supporting line, held to a readable measure so it stays legible when the box is a full-width region. |
EmptyState | icon | ReactNode | — | The glyph above the title, sized by the component’s own slot rule. There is no default: which glyph belongs follows from the product, not from the type. |
EmptyState | titleAs | "h1" | "h2" | "h3" | "h4" | "p" | "h3" | What the title renders as. It changes the element and nothing else, so pick it for the outline of the page around the box. |
EmptyState | filled | boolean | false | Tinted fill, so the box reads as a surface of its own. |
EmptyState | droppable | boolean | false | Dashed border marking a drop target. It draws the promise; the handler is yours. |
EmptyState | fill | boolean | false | Grows into its region and centres. It needs a flex-column parent — without one there is nothing to grow into. |
EmptyState | children | ReactNode | — | The action area, below the copy. It sits in the root’s own column, so a Button and a hint need no wrapper between them. |
EmptyState | EmptyStateHint | <EmptyStateHint /> | — | The help link, exported beside the component and passed in children. It is an anchor and takes everything one takes; the package supplies only its type scale, its colour and its focus ring. |
EmptyStateHint | children | ReactNode | — | The link text. It is read out of the page’s flow as often as in it, so it has to name its destination rather than lean on the copy above. |