Products · Build a dot
Components
Pick the right Dot component for each part of a page, from layout, text and lists to forms, amounts, feedback and sheets.
Components look and behave like the rest of Dash. Each name below links to its page in the Components reference, which lists every prop and event.
Every prop is optional. If you use a prop that a component doesn't have, the build tells you. Events work as described in Views.
Lay out a page
| Component | Use it for |
|---|---|
View | Columns, rows and cards. See Layout and styling |
Stack | Text over a picture, and other layers. See Layers with Stack |
Grid, GridItem | A home screen of services, in equal columns |
Scroll | A row that scrolls sideways |
Carousel | Slides, one at a time |
Footer | The page's main button, kept at the bottom while the page scrolls. Put it last |
Pinned | Anything else kept on the top or bottom edge, such as the page's own tabs |
Divider | A line between sections |
Show text, amounts and pictures
| Component | Use it for |
|---|---|
Text | All text. variant sets its role, from display for a big amount down to caption |
Link | Text to tap. An https href opens in the phone's browser |
Image | A picture from statics/, or from a host in network.hosts |
Avatar | A photo in a circle, or initials until it loads |
Icon | One of Dash's icons |
Badge | A short status, like Paid |
QRCode | A ticket or other code for someone else to scan |
<View variant="card" gap="small">
<View direction="row" gap="medium" align="center">
<Avatar name="Mariama Kamara" image="{{ photo }}" />
<Text variant="subtitle">Mariama Kamara</Text>
</View>
<Text variant="caption" tone="muted">Balance</Text>
<Text variant="display" numeric>SLE {{ balance }}</Text>
</View>Set numeric on amounts so the digits line up. Give an Image an aspect, and its height follows from its width.
Lists and details
A List holds ListItem rows in a card. For a receipt, use a DetailRow for each label and its value. A Disclosure is a row that opens to show more, like an FAQ answer. Steps with Step show what has happened and what comes next.
This list of payments loads more as the person scrolls down:
<List onEndReached="loadMore">
<For each="{{ payments }}" item="payment" key="id">
<ListItem
icon="receipt"
title="{{ payment.name }}"
value="SLE {{ payment.amount }}"
data-id="{{ payment.id }}"
onTap="open"
/>
</For>
</List>loadMore returns early while a batch is loading, and after the last one:
async loadMore(state) {
if (state.loading || state.allLoaded) {
return;
}
this.setState({ loading: true, error: '' });
try {
const url = `${env.API_URL}/payments?skip=${state.payments.length}`;
const response = await this.dash.network.http.request<Payment[]>({ url, as: 'json' });
if (!response.ok) {
throw new Error();
}
this.setState({
payments: [...this.state.payments, ...response.body],
allLoaded: response.body.length === 0,
});
} catch {
this.setState({ error: "We couldn't load more payments." });
} finally {
this.setState({ loading: false });
}
},Long text to read
For news and guides, wrap your text in Prose for comfortable spacing. It works with Quote and BulletList. For Markdown from your server, use RichText, which is safe: HTML shows as plain text, and only https links open.
Forms
| Component | Use it for |
|---|---|
Input | One line of text. type picks the keyboard |
TextArea | Several lines of text |
Select, Option | One choice from a long list |
RadioGroup, Radio | One choice from a few |
Switch, Checkbox | On or off |
Stepper, Slider | A number |
Button | An action. variant is primary, secondary, outline or ghost, tone colors it, like danger for one that deletes, and size makes it smaller or larger. loading shows a spinner and ignores taps |
A field shows what's in state. Pass the value in, and update it from the field's change event. This one sends airtime to a cousin in Kenema:
<Input
label="Phone number"
type="phone"
prefix="+232"
value="{{ phone }}"
error="{{ phoneError }}"
onChange="setPhone"
/>
<Select label="Network" value="{{ network }}" onChange="setNetwork">
<Option value="orange" label="Orange" />
<Option value="africell" label="Africell" />
<Option value="qcell" label="Qcell" />
</Select>
<Switch label="Save this number" checked="{{ save }}" onChange="setSave" />
<Button onClick="send" loading="{{ sending }}">Send airtime</Button>mutations: {
setPhone: (_state, event: ValueEvent) => ({ phone: event.detail.value, phoneError: '' }),
setNetwork: (_state, event: ValueEvent) => ({ network: event.detail.value }),
setSave: (_state, event: CheckedEvent) => ({ save: event.detail.checked }),
},
actions: {
async send(state) {
if (state.phone.replace(/\D/g, '').length !== 8) {
this.setState({ phoneError: 'Enter 8 digits, such as 76 123456.' });
return;
}
// Send the airtime.
},
},Set error to show a problem under a field, as send does. For a search box, use type="search" and see Search as the person types.
Amounts and codes
AmountInput takes a sum of money. CodeInput takes a one-time code or PIN, and picks up a code the phone reads from an SMS.
<AmountInput label="Amount" value="{{ amount }}" onChange="setAmount" />
<CodeInput
label="Enter the code we sent you"
value="{{ code }}"
onChange="setCode"
onComplete="verify"
/>AmountInputcounts in the smallest unit, like the Monime API, so150is SLE 1.50. Setunit="major"to count whole amounts, so150is SLE 150. It sendsnullwhen empty, so type its event asValueEvent<number | null>.CodeInputsendscompleteonce the last digit is in, so check the code there.
Tabs and filters
Tabs switch between views of one page. Chips narrow a list. Tabs only records which tab is chosen, so show each tab's content with an <If>:
<Tabs value="{{ tab }}" variant="segmented" onChange="setTab">
<Tab value="sent" label="Sent" />
<Tab value="received" label="Received" badge="{{ unread }}" />
</Tabs>
<If condition="{{ tab === 'sent' }}">
<Text>Money you sent</Text>
</If>
<Else>
<Text>Money you received</Text>
</Else>A Tab can also have an icon, which shows before its label. A disabled Tab can't be chosen.
A page's own navigation
For tabs that stay on screen, put Tabs with variant="bar" in a Pinned. The tabs share the width equally, with each icon over its label and each badge on its icon's corner:
<View>
<If condition="{{ tab === 'home' }}">
<Text>Today's specials</Text>
</If>
<ElseIf condition="{{ tab === 'orders' }}">
<Text>Your orders</Text>
</ElseIf>
<Else>
<Text>Your account</Text>
</Else>
<Pinned edge="bottom" border="subtle">
<Tabs variant="bar" value="{{ tab }}" onChange="setTab">
<Tab value="home" label="Home" icon="home" />
<Tab value="orders" label="Orders" icon="receipt" badge="{{ newOrders }}" />
<Tab value="account" label="Account" icon="user" />
</Tabs>
</Pinned>
</View>
Set tabBar: { hidden: true } in the page's configs, so Dash's tab bar doesn't show under yours.
Feedback
| Component | Use it for |
|---|---|
Spinner | Something's loading |
Skeleton | A placeholder shaped like what's loading |
ProgressBar | How far along an upload is, from 0 to 100 |
Notice | A message that stays on the page |
EmptyState | What to show when there's nothing yet |
<EmptyState icon="receipt" title="No payments yet" text="Payments you make show here.">
<Button onClick="pay">Make a payment</Button>
</EmptyState>Sheets and toasts
A Sheet slides up over the page. A Toast shows a quick message, like "Copied". Both follow the state:
- A
Sheetshows whileopenis true. Tapping outside it sendsclose, and your handler setsopenback to false. - A
Toastshows itsmessage, then sendshide. Clear the message there so it can show again.
<Sheet open="{{ selected !== null }}" title="Payment" onClose="closeSheet">
<Button variant="secondary" onClick="copyReference">Copy reference</Button>
<Button onClick="closeSheet">Cancel</Button>
</Sheet>
<Toast message="{{ toast }}" tone="success" onHide="clearToast" />