Component library · 0.1

One shared language.

Native HTML foundations. Progressive enhancement. The same components from signup to the counter.

Use your browser’s JavaScript setting to review the native fallbacks. Every example and its HTML contract are present before scripts run.

The components below are written once, in Rust (crates/ui). Server pages render them through maud; the POS imports the Solid components cargo xtask ui translates from them. Each specimen is the Rust component's output, and the browser suite checks the Solid component renders the same.

Button

A button for an action on the page. One that goes somewhere is a LinkButton: a link, not a button.

Props
PropTypeAbout
childrenSlotRequired. What it says: the action, as a verb ("Save", "Delete store").
variantOption<ButtonVariant (primary | danger | quiet)>How much a button stands out. Without one it is secondary: most buttons are, and a page has at most one primary.
sizeOption<ControlSize (compact | touch)>A control's size. Without one it is the normal size.
kindButtonType (button | submit | reset)Defaults to button, so it never submits a form by accident. Default: button.
blockboolFills its container's width. Default: false.
disabledboolDefault: false.
busyboolMarks it working (a save in flight): the cursor shows progress. Default: false.
restAttrs
Primary
Rust, Solid and HTML

Rust

Button::new("Save changes")
    .variant(ButtonVariant::Primary)

Solid

<Button variant="primary">Save changes</Button>

HTML

<button class="mk-btn" type="button" data-variant="primary">Save changes</button>
Secondary
Rust, Solid and HTML

Rust

Button::new("Cancel")

Solid

<Button>Cancel</Button>

HTML

<button class="mk-btn" type="button">Cancel</button>
Danger
Rust, Solid and HTML

Rust

Button::new("Delete store")
    .variant(ButtonVariant::Danger)

Solid

<Button variant="danger">Delete store</Button>

HTML

<button class="mk-btn" type="button" data-variant="danger">Delete store</button>
Quiet
Rust, Solid and HTML

Rust

Button::new("Discard")
    .variant(ButtonVariant::Quiet)

Solid

<Button variant="quiet">Discard</Button>

HTML

<button class="mk-btn" type="button" data-variant="quiet">Discard</button>
Submits its form
Rust, Solid and HTML

Rust

Button::new("Log in")
    .variant(ButtonVariant::Primary)
    .kind(ButtonType::Submit)

Solid

<Button variant="primary" kind="submit">Log in</Button>

HTML

<button class="mk-btn" type="submit" data-variant="primary">Log in</button>
Compact
Rust, Solid and HTML

Rust

Button::new("Discard")
    .size(ControlSize::Compact)

Solid

<Button size="compact">Discard</Button>

HTML

<button class="mk-btn" type="button" data-size="compact">Discard</button>
Touch
Rust, Solid and HTML

Rust

Button::new("Charge")
    .variant(ButtonVariant::Primary)
    .size(ControlSize::Touch)
    .block(true)

Solid

<Button variant="primary" size="touch" block>Charge</Button>

HTML

<button class="mk-btn" type="button" data-variant="primary" data-size="touch" data-block>Charge</button>
Disabled
Rust, Solid and HTML

Rust

Button::new("Export orders")
    .disabled(true)

Solid

<Button disabled>Export orders</Button>

HTML

<button class="mk-btn" type="button" disabled>Export orders</button>
Busy
Rust, Solid and HTML

Rust

Button::new("Saving…")
    .variant(ButtonVariant::Primary)
    .disabled(true)
    .busy(true)

Solid

<Button variant="primary" disabled busy>Saving…</Button>

HTML

<button class="mk-btn" type="button" data-variant="primary" disabled aria-busy="true">Saving…</button>

LaunchTile

A tile that opens a tool (the POS, an order form): an icon, a title and a line saying what it's for. Without href it can't be opened yet (a script checks the browser first): marked disabled, the line saying why, until the page gives it somewhere to go.

Props
PropTypeAbout
titleStringRequired.
hintStringRequired. What it's for, or why it can't be opened.
iconSlotRequired. An inline SVG.
hrefOption<String>
hint_idOption<String>The hint's id, for a script that rewrites it.
restAttrs
Ready
Rust, Solid and HTML

Rust

LaunchTile::new("Create an order", "For one-off custom payments", "+")
    .href("#component-launch-tile")

Solid

<LaunchTile title="Create an order" hint="For one-off custom payments" icon={{
  "$slot": [
    "+"
  ]
}} href="#component-launch-tile" />

HTML

<a class="mk-launch-tile" href="#component-launch-tile"><span class="mk-launch-tile-icon">+</span><span class="mk-launch-tile-text"><span class="mk-launch-tile-title">Create an order</span><span class="mk-launch-tile-hint">For one-off custom payments</span></span></a>
Waiting for a script
Rust, Solid and HTML

Rust

LaunchTile::new("POS Terminal", "Requires JavaScript", "▦")

Solid

<LaunchTile title="POS Terminal" hint="Requires JavaScript" icon={{
  "$slot": [
    "▦"
  ]
}} />

HTML

<a class="mk-launch-tile" aria-disabled="true"><span class="mk-launch-tile-icon">▦</span><span class="mk-launch-tile-text"><span class="mk-launch-tile-title">POS Terminal</span><span class="mk-launch-tile-hint">Requires JavaScript</span></span></a>

SegmentedControl

A choice of one view among a few (Active and Finished orders): its buttons are Buttons with aria-pressed, the pressed one shown. They filter what's on the page; panels to switch between are Tabs.

Props
PropTypeAbout
labelStringRequired. What is chosen ("Order status").
childrenSlotRequired. The Buttons, each aria-pressed.
Orders by status
Rust, Solid and HTML

Rust

SegmentedControl::new("Order status", Slot::many([Button::new("Active · 3")
    .attr("aria-pressed", "true"), Button::new("Finished · 12")
    .attr("aria-pressed", "false")]))

Solid

<SegmentedControl label="Order status"><Button aria-pressed="true">Active · 3</Button><Button aria-pressed="false">Finished · 12</Button></SegmentedControl>

HTML

<div class="mk-segmented" role="group" aria-label="Order status"><button class="mk-btn" type="button" aria-pressed="true">Active · 3</button><button class="mk-btn" type="button" aria-pressed="false">Finished · 12</button></div>

Alert

A message about the page or what just happened: a refused save, a node that's down, a save that went through. An error is announced (role="alert"), the rest are statuses, unless quiet. A message that asks for something takes a title and actions (Dismiss, a link to fix it).

Props
PropTypeAbout
childrenSlotRequired.
toneTone (error | warning | success | notice)Required. What a message says about how something went. The words say it; the colour only backs them up.
iconOption<Slot>Before everything, at the left: an icon (assets::icons::INFO).
titleOption<String>Bold, before the words.
actionsOption<Slot>Under the words: buttons or links.
quietboolNot announced: a message that is part of the page as it loads. Default: false.
blockboolA div, for a message holding lists or paragraphs; a p otherwise. Default: false.
restAttrs
Error
Rust, Solid and HTML

Rust

Alert::new("Could not save. Check the highlighted fields and try again.", Tone::Error)

Solid

<Alert tone="error">Could not save. Check the highlighted fields and try again.</Alert>

HTML

<p class="error" role="alert">Could not save. Check the highlighted fields and try again.</p>
Warning

Connection interrupted. Payment status may be delayed.

Rust, Solid and HTML

Rust

Alert::new("Connection interrupted. Payment status may be delayed.", Tone::Warning)

Solid

<Alert tone="warning">Connection interrupted. Payment status may be delayed.</Alert>

HTML

<p class="warning" role="status">Connection interrupted. Payment status may be delayed.</p>
Success

Saved.

Rust, Solid and HTML

Rust

Alert::new("Saved.", Tone::Success)

Solid

<Alert tone="success">Saved.</Alert>

HTML

<p class="success" role="status">Saved.</p>
Notice

No wallets yet.

Rust, Solid and HTML

Rust

Alert::new("No wallets yet.", Tone::Notice)
    .quiet(true)

Solid

<Alert tone="notice" quiet>No wallets yet.</Alert>

HTML

<p class="notice">No wallets yet.</p>
Asking for something
Any website can show this store's checkout If a scam site embeds it, that site's victims would pay you. Verify your domains in Settings and allow only them.
Rust, Solid and HTML

Rust

Alert::new("If a scam site embeds it, that site's victims would pay you. Verify your domains in Settings and allow only them.", Tone::Warning)
    .title("Any website can show this store's checkout")
    .actions(Button::new("Dismiss")
        .size(ControlSize::Compact))
    .quiet(true)
    .block(true)

Solid

<Alert tone="warning" title="Any website can show this store's checkout" actions={{
  "$slot": [
    {
      "$component": "Button",
      "props": {
        "block": false,
        "busy": false,
        "children": {
          "$slot": [
            "Dismiss"
          ]
        },
        "disabled": false,
        "kind": "button",
        "size": "compact"
      }
    }
  ]
}} quiet block>If a scam site embeds it, that site's victims would pay you. Verify your domains in Settings and allow only them.</Alert>

HTML

<div class="warning"><strong class="alert-title">Any website can show this store's checkout</strong> If a scam site embeds it, that site's victims would pay you. Verify your domains in Settings and allow only them.<span class="alert-actions"><button class="mk-btn" type="button" data-size="compact">Dismiss</button></span></div>

Panel

A panel holding one part of a page: a form, a set of facts; or, as a tile, one of a grid of them.

Props
PropTypeAbout
childrenSlotRequired.
lookPanelLook (raised | tile)A panel's look. Default: raised.
restAttrs
A tile

Watch-only

Each store gives Monokulo its view key, never its spend key.

Rust, Solid and HTML

Rust

Panel::new(Slot::many([Heading::new("Watch-only")
    .level(HeadingLevel::H3), Hint::new("Each store gives Monokulo its view key, never its spend key.")]))
    .look(PanelLook::Tile)

Solid

<Panel look="tile"><Heading level="h3">Watch-only</Heading><Hint>Each store gives Monokulo its view key, never its spend key.</Hint></Panel>

HTML

<div class="box" data-look="tile"><h3>Watch-only</h3><p class="hint">Each store gives Monokulo its view key, never its spend key.</p></div>
A part of a page

Webhook

Monokulo posts each order's changes here.

Rust, Solid and HTML

Rust

Panel::new(Slot::many([Heading::new("Webhook")
    .level(HeadingLevel::H3), Hint::new("Monokulo posts each order's changes here.")]))

Solid

<Panel><Heading level="h3">Webhook</Heading><Hint>Monokulo posts each order's changes here.</Hint></Panel>

HTML

<div class="box"><h3>Webhook</h3><p class="hint">Monokulo posts each order's changes here.</p></div>

Heading

A section's or a part's heading, under the page's PageHeader.

Props
PropTypeAbout
childrenSlotRequired.
levelHeadingLevel (h2 | h3 | h4)A heading's level. A page's own title, the h1, is its PageHeader's. Default: h2.
idOption<String>
A section's

Recent orders

Rust, Solid and HTML

Rust

Heading::new("Recent orders")

Solid

<Heading>Recent orders</Heading>

HTML

<h2>Recent orders</h2>
A part's

Webhook

Rust, Solid and HTML

Rust

Heading::new("Webhook")
    .level(HeadingLevel::H3)

Solid

<Heading level="h3">Webhook</Heading>

HTML

<h3>Webhook</h3>

Hint

Muted help beside or under what it explains. A span inside a line of text when inline; a paragraph otherwise.

Props
PropTypeAbout
childrenSlotRequired.
inlineboolDefault: false.
restAttrs
Under a field

Shown to customers on the payment page.

Rust, Solid and HTML

Rust

Hint::new("Shown to customers on the payment page.")

Solid

<Hint>Shown to customers on the payment page.</Hint>

HTML

<p class="hint">Shown to customers on the payment page.</p>
In a line
(optional)
Rust, Solid and HTML

Rust

Hint::new("(optional)")
    .inline(true)

Solid

<Hint inline>(optional)</Hint>

HTML

<span class="hint">(optional)</span>

Facts

Facts about one thing, each named: a wallet's network, an order's amount.

Props
PropTypeAbout
factsVec<Fact>Required. One fact of [Facts].
restAttrs
A wallet
Network
Mainnet
Watching since
block 3,412,001
Balance
1.25 XMR
Rust, Solid and HTML

Rust

Facts::new(vec![
    Fact { term: "Network".into(), value: NetworkBadge::new("Mainnet")
        .main(true).into() },
    Fact { term: "Watching since".into(), value: "block 3,412,001".into() },
    Fact { term: "Balance".into(), value: "1.25 XMR".into() },
])

Solid

<Facts facts={[
  {
    "term": "Network",
    "value": {
      "$slot": [
        {
          "$component": "NetworkBadge",
          "props": {
            "icon_only": false,
            "main": true,
            "name": "Mainnet"
          }
        }
      ]
    }
  },
  {
    "term": "Watching since",
    "value": {
      "$slot": [
        "block 3,412,001"
      ]
    }
  },
  {
    "term": "Balance",
    "value": {
      "$slot": [
        "1.25 XMR"
      ]
    }
  }
]} />

HTML

<dl class="facts"><dt>Network</dt><dd><span class="tag-network is-main"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M7 15V9l5 5 5-5v6"></path></svg><span class="tag-network-name">Mainnet</span></span></dd><dt>Watching since</dt><dd>block 3,412,001</dd><dt>Balance</dt><dd>1.25 XMR</dd></dl>

Tag

A short word or two about something's state: a node's health, an order's status. short is what a phone shows instead, the long words kept for screen readers.

Props
PropTypeAbout
childrenSlotRequired.
toneOption<TagTone (ok | error | unknown | syncing | stalled | slow | muted | dashed)>How a tag reads, beside its words.
stateOption<OrderState (pending | unconfirmed | confirming | partial | paid | overpaid | expired | cancelled | double-spend | offline | unknown)>An order's state: each its own colours, the same wherever an order's state is shown (the checkout, the POS, the dashboard).
shortOption<String>
iconOption<Slot>Before the words: an inline SVG or a status symbol.
restAttrs
Plain
Default
Rust, Solid and HTML

Rust

Tag::new("Default")

Solid

<Tag>Default</Tag>

HTML

<span class="tag">Default</span>
Healthy
Healthy
Rust, Solid and HTML

Rust

Tag::new("Healthy")
    .tone(TagTone::Ok)

Solid

<Tag tone="ok">Healthy</Tag>

HTML

<span class="tag tag-ok">Healthy</span>
Down
Down
Rust, Solid and HTML

Rust

Tag::new("Down")
    .tone(TagTone::Error)

Solid

<Tag tone="error">Down</Tag>

HTML

<span class="tag tag-error">Down</span>
Not checked
Not checked
Rust, Solid and HTML

Rust

Tag::new("Not checked")
    .tone(TagTone::Unknown)

Solid

<Tag tone="unknown">Not checked</Tag>

HTML

<span class="tag tag-unknown">Not checked</span>
Syncing
Syncing
Rust, Solid and HTML

Rust

Tag::new("Syncing")
    .tone(TagTone::Syncing)

Solid

<Tag tone="syncing">Syncing</Tag>

HTML

<span class="tag tag-syncing">Syncing</span>
Slow
Slow
Rust, Solid and HTML

Rust

Tag::new("Slow")
    .tone(TagTone::Slow)

Solid

<Tag tone="slow">Slow</Tag>

HTML

<span class="tag tag-slow">Slow</span>
Muted, with an icon
Options file
Rust, Solid and HTML

Rust

Tag::new("Options file")
    .tone(TagTone::Muted)
    .icon("⚙")

Solid

<Tag tone="muted" icon={{
  "$slot": [
    "⚙"
  ]
}}>Options file</Tag>

HTML

<span class="tag tag-muted"><span class="tag-icon" aria-hidden="true">⚙</span>Options file</span>
Dashed: a default
Default
Rust, Solid and HTML

Rust

Tag::new("Default")
    .tone(TagTone::Dashed)

Solid

<Tag tone="dashed">Default</Tag>

HTML

<span class="tag tag-dashed">Default</span>
An order waiting, shorter on a phone
Waiting for payment
Rust, Solid and HTML

Rust

Tag::new("Waiting for payment")
    .state(OrderState::Pending)
    .short("Waiting")

Solid

<Tag state="pending" short="Waiting">Waiting for payment</Tag>

HTML

<span class="tag state-pending"><span class="label-long">Waiting for payment</span><span class="label-short" aria-hidden="true">Waiting</span></span>
An order seen
Payment seen, unconfirmed
Rust, Solid and HTML

Rust

Tag::new("Payment seen, unconfirmed")
    .state(OrderState::Unconfirmed)
    .short("Seen")

Solid

<Tag state="unconfirmed" short="Seen">Payment seen, unconfirmed</Tag>

HTML

<span class="tag state-unconfirmed"><span class="label-long">Payment seen, unconfirmed</span><span class="label-short" aria-hidden="true">Seen</span></span>
An order confirming
Confirming
Rust, Solid and HTML

Rust

Tag::new("Confirming")
    .state(OrderState::Confirming)

Solid

<Tag state="confirming">Confirming</Tag>

HTML

<span class="tag state-confirming">Confirming</span>
An order paid
Paid
Rust, Solid and HTML

Rust

Tag::new("Paid")
    .state(OrderState::Paid)

Solid

<Tag state="paid">Paid</Tag>

HTML

<span class="tag state-paid">Paid</span>
An order expired
Expired
Rust, Solid and HTML

Rust

Tag::new("Expired")
    .state(OrderState::Expired)

Solid

<Tag state="expired">Expired</Tag>

HTML

<span class="tag state-expired">Expired</span>
An order cancelled
Cancelled
Rust, Solid and HTML

Rust

Tag::new("Cancelled")
    .state(OrderState::Cancelled)

Solid

<Tag state="cancelled">Cancelled</Tag>

HTML

<span class="tag state-cancelled">Cancelled</span>

NetworkBadge

A Monero network, the same wherever a network is shown: the word always, with an icon and an edge to back it up. Mainnet (real money) has the brand's orange tint, a coin and a solid edge; a test network a neutral tint, a flask and a dashed edge.

Props
PropTypeAbout
nameStringRequired. The network's name ("Mainnet", "Stagenet", "Test networks").
mainboolDefault: false.
icon_onlyboolOnly the icon and the edge, its name for screen readers and on hover: in a table's cell, beside a wallet's name. Default: false.
Mainnet
Mainnet
Rust, Solid and HTML

Rust

NetworkBadge::new("Mainnet")
    .main(true)

Solid

<NetworkBadge name="Mainnet" main />

HTML

<span class="tag-network is-main"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M7 15V9l5 5 5-5v6"></path></svg><span class="tag-network-name">Mainnet</span></span>
A test network
Stagenet
Rust, Solid and HTML

Rust

NetworkBadge::new("Stagenet")

Solid

<NetworkBadge name="Stagenet" />

HTML

<span class="tag-network is-test"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3h6M10 3v6l-5 9a2 2 0 0 0 1.7 3h10.6a2 2 0 0 0 1.7-3l-5-9V3"></path><path d="M7.5 15h9"></path></svg><span class="tag-network-name">Stagenet</span></span>
Mainnet, icon only
Mainnet
Rust, Solid and HTML

Rust

NetworkBadge::new("Mainnet")
    .main(true)
    .icon_only(true)

Solid

<NetworkBadge name="Mainnet" main icon_only />

HTML

<span class="tag-network is-main" data-size="icon" title="Mainnet"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M7 15V9l5 5 5-5v6"></path></svg><span class="tag-network-name">Mainnet</span></span>
A test network, icon only
Stagenet
Rust, Solid and HTML

Rust

NetworkBadge::new("Stagenet")
    .icon_only(true)

Solid

<NetworkBadge name="Stagenet" icon_only />

HTML

<span class="tag-network is-test" data-size="icon" title="Stagenet"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3h6M10 3v6l-5 9a2 2 0 0 0 1.7 3h10.6a2 2 0 0 0 1.7-3l-5-9V3"></path><path d="M7.5 15h9"></path></svg><span class="tag-network-name">Stagenet</span></span>

ShortValue

A long value shortened on screen (an id, an address) whose whole value is still what a double-click selects, a copy takes, a screen reader reads and find-in-page matches: the short text shows, and the whole value lies over it, transparent. Short enough already, it is just the text.

Props
PropTypeAbout
valueStringRequired.
shortStringRequired. What shows.
An order id
order_a8723b2e45b0d44ea8723b2e45b0d44e
Rust, Solid and HTML

Rust

ShortValue::new("order_a8723b2e45b0d44ea8723b2e45b0d44e", "a8723b…b0d44e")

Solid

<ShortValue value="order_a8723b2e45b0d44ea8723b2e45b0d44e" short="a8723b…b0d44e" />

HTML

<span class="short-value" title="order_a8723b2e45b0d44ea8723b2e45b0d44e"><span class="short-value-text" aria-hidden="true">a8723b…b0d44e</span><span class="short-value-full">order_a8723b2e45b0d44ea8723b2e45b0d44e</span></span>
Short enough
a8723b
Rust, Solid and HTML

Rust

ShortValue::new("a8723b", "a8723b")

Solid

<ShortValue value="a8723b" short="a8723b" />

HTML

a8723b

Disclosure

More to see behind a summary: a details, so it opens without JavaScript.

Props
PropTypeAbout
summarySlotRequired.
childrenSlotRequired.
lookDisclosureLook (plain | boxed | more-options | compact | row)A disclosure's look. Default: plain.
openboolDefault: false.
alertboolSomething in it needs attention: an error's edge (open it too). Default: false.
restAttrs
Plain
How it's checkedEvery block is checked against two nodes.
Rust, Solid and HTML

Rust

Disclosure::new("How it's checked", "Every block is checked against two nodes.")

Solid

<Disclosure summary={{
  "$slot": [
    "How it's checked"
  ]
}}>Every block is checked against two nodes.</Disclosure>

HTML

<details class="disclosure"><summary>How it's checked</summary>Every block is checked against two nodes.</details>
Boxed help
Where do I find these keys?
Settings, then Keys.
Rust, Solid and HTML

Rust

Disclosure::new("Where do I find these keys?", "Settings, then Keys.")
    .look(DisclosureLook::Boxed)

Solid

<Disclosure summary={{
  "$slot": [
    "Where do I find these keys?"
  ]
}} look="boxed">Settings, then Keys.</Disclosure>

HTML

<details class="keys-help"><summary>Where do I find these keys?</summary><div class="keys-help-body">Settings, then Keys.</div></details>
More options
More optionsLeave on mainnet unless this is a test wallet.
Rust, Solid and HTML

Rust

Disclosure::new("More options", "Leave on mainnet unless this is a test wallet.")
    .look(DisclosureLook::MoreOptions)

Solid

<Disclosure summary={{
  "$slot": [
    "More options"
  ]
}} look="more-options">Leave on mainnet unless this is a test wallet.</Disclosure>

HTML

<details class="more-options"><summary>More options</summary>Leave on mainnet unless this is a test wallet.</details>
Compact
Scanning 1 block behind
Progress
1 block behind
Requests
None yet
Rust, Solid and HTML

Rust

Disclosure::new(Slot::many([Tag::new("Scanning")
    .tone(TagTone::Syncing), " 1 block behind"]), Facts::new(vec![
    Fact { term: "Progress".into(), value: "1 block behind".into() },
    Fact { term: "Requests".into(), value: "None yet".into() },
]))
    .look(DisclosureLook::Compact)
    .open(true)

Solid

<Disclosure summary={{
  "$slot": [
    {
      "$component": "Tag",
      "props": {
        "children": {
          "$slot": [
            "Scanning"
          ]
        },
        "tone": "syncing"
      }
    },
    " 1 block behind"
  ]
}} look="compact" open><Facts facts={[
  {
    "term": "Progress",
    "value": {
      "$slot": [
        "1 block behind"
      ]
    }
  },
  {
    "term": "Requests",
    "value": {
      "$slot": [
        "None yet"
      ]
    }
  }
]} /></Disclosure>

HTML

<details class="disclosure-compact" open><summary><span class="tag tag-syncing">Scanning</span> 1 block behind</summary><div class="disclosure-body"><dl class="facts"><dt>Progress</dt><dd>1 block behind</dd><dt>Requests</dt><dd>None yet</dd></dl></div></details>
A row
Primary · node.example.com:18081
The node's address, and whether it uses TLS.
Rust, Solid and HTML

Rust

Disclosure::new("Primary · node.example.com:18081", "The node's address, and whether it uses TLS.")
    .look(DisclosureLook::Row)

Solid

<Disclosure summary={{
  "$slot": [
    "Primary · node.example.com:18081"
  ]
}} look="row">The node's address, and whether it uses TLS.</Disclosure>

HTML

<details class="disclosure-row"><summary>Primary · node.example.com:18081</summary><div class="disclosure-body">The node's address, and whether it uses TLS.</div></details>

HelpTip

A "?" that opens help over the page: a details, so it opens without JavaScript. Once its island attaches, a click outside it or Escape closes it, Escape putting focus back on the "?".

Props
PropTypeAbout
labelStringRequired. What the help is about ("What the page shows"): the "?"'s name.
childrenSlotRequired.
idOption<String>
wideboolRoom for two columns of help, as wide as the page allows. Default: false.
anchorTipAnchor (tip | container)Where the help opens from. Default: tip.
restAttrs
Search syntax
?
Fields compare with =, !=, <, >; join them with and, or. Quote text: order.id = 'o_1'.
Rust, Solid and HTML

Rust

HelpTip::new("How to search", "Fields compare with =, !=, <, >; join them with and, or. Quote text: order.id = 'o_1'.")

Solid

<HelpTip label="How to search">Fields compare with =, !=, <, >; join them with and, or. Quote text: order.id = 'o_1'.</HelpTip>

HTML

<details class="help-tip" data-mk="1" data-island="HelpTip" data-props="{}"><summary aria-label="How to search" title="How to search">?</summary><div class="help-tip-body">Fields compare with =, !=, &lt;, &gt;; join them with and, or. Quote text: order.id = 'o_1'.</div></details>

SortableList

A list whose items can be put in another order. Without JavaScript the page reorders it (each item's own Move up and Move down, posted to the server). Once its island attaches, an item moves by dragging its SortHandle, or with the up and down arrows on the handle; the items it passes slide out of its way. Items marked fixed (a blank row to add one) stay at the end.

Each move sends mk-reorder from the list ({item}), for a page script to renumber what it names after the order; a drag that ends in a new order, or an arrow, sends mk-reordered ({item}).

Props
PropTypeAbout
labelStringRequired. What is ordered ("Stagenet nodes").
childrenSlotRequired. SortableItems.
restAttrs
Nodes, tried in order
  1. ⠿Primary · node-a.example:18081
    The node's address, and whether it uses TLS.
  2. ⠿Fallback 1 · node-b.example:18081
    The node's address, and whether it uses TLS.
  3. ⠿Fallback 2 · node-c.example:18081
    The node's address, and whether it uses TLS.
  4. + Add a node
    An address to try last.
Rust, Solid and HTML

Rust

SortableList::new("Example nodes", Slot::many([SortableItem::new(Disclosure::new(Slot::many([SortHandle::new("Reorder Primary: press the up or down arrow"), "Primary · node-a.example:18081"]), Slot::many(["The node's address, and whether it uses TLS.", FormActions::new(Button::new("Move down")
    .attr("data-only", "no-script"))]))
    .look(DisclosureLook::Row)), SortableItem::new(Disclosure::new(Slot::many([SortHandle::new("Reorder Fallback 1: press the up or down arrow"), "Fallback 1 · node-b.example:18081"]), Slot::many(["The node's address, and whether it uses TLS.", FormActions::new(Slot::many([Button::new("Move up")
    .attr("data-only", "no-script"), Button::new("Move down")
    .attr("data-only", "no-script")]))]))
    .look(DisclosureLook::Row)), SortableItem::new(Disclosure::new(Slot::many([SortHandle::new("Reorder Fallback 2: press the up or down arrow"), "Fallback 2 · node-c.example:18081"]), Slot::many(["The node's address, and whether it uses TLS.", FormActions::new(Button::new("Move up")
    .attr("data-only", "no-script"))]))
    .look(DisclosureLook::Row)), SortableItem::new(Disclosure::new("+ Add a node", "An address to try last.")
    .look(DisclosureLook::Row))
    .fixed(true)]))

Solid

<SortableList label="Example nodes"><SortableItem><Disclosure summary={{
  "$slot": [
    {
      "$component": "SortHandle",
      "props": {
        "label": "Reorder Primary: press the up or down arrow"
      }
    },
    "Primary · node-a.example:18081"
  ]
}} look="row">The node's address, and whether it uses TLS.<FormActions><Button data-only="no-script">Move down</Button></FormActions></Disclosure></SortableItem><SortableItem><Disclosure summary={{
  "$slot": [
    {
      "$component": "SortHandle",
      "props": {
        "label": "Reorder Fallback 1: press the up or down arrow"
      }
    },
    "Fallback 1 · node-b.example:18081"
  ]
}} look="row">The node's address, and whether it uses TLS.<FormActions><Button data-only="no-script">Move up</Button><Button data-only="no-script">Move down</Button></FormActions></Disclosure></SortableItem><SortableItem><Disclosure summary={{
  "$slot": [
    {
      "$component": "SortHandle",
      "props": {
        "label": "Reorder Fallback 2: press the up or down arrow"
      }
    },
    "Fallback 2 · node-c.example:18081"
  ]
}} look="row">The node's address, and whether it uses TLS.<FormActions><Button data-only="no-script">Move up</Button></FormActions></Disclosure></SortableItem><SortableItem fixed><Disclosure summary={{
  "$slot": [
    "+ Add a node"
  ]
}} look="row">An address to try last.</Disclosure></SortableItem></SortableList>

HTML

<ol class="mk-sortable" aria-label="Example nodes" data-mk="1" data-island="SortableList" data-props="{}"><li class="mk-sortable-item"><details class="disclosure-row"><summary><span class="mk-sort-handle" role="button" tabindex="0" title="Drag to reorder" aria-label="Reorder Primary: press the up or down arrow" data-sortable-handle="" data-only="script">⠿</span>Primary · node-a.example:18081</summary><div class="disclosure-body">The node's address, and whether it uses TLS.<div class="form-actions"><button class="mk-btn" type="button" data-only="no-script">Move down</button></div></div></details></li><li class="mk-sortable-item"><details class="disclosure-row"><summary><span class="mk-sort-handle" role="button" tabindex="0" title="Drag to reorder" aria-label="Reorder Fallback 1: press the up or down arrow" data-sortable-handle="" data-only="script">⠿</span>Fallback 1 · node-b.example:18081</summary><div class="disclosure-body">The node's address, and whether it uses TLS.<div class="form-actions"><button class="mk-btn" type="button" data-only="no-script">Move up</button><button class="mk-btn" type="button" data-only="no-script">Move down</button></div></div></details></li><li class="mk-sortable-item"><details class="disclosure-row"><summary><span class="mk-sort-handle" role="button" tabindex="0" title="Drag to reorder" aria-label="Reorder Fallback 2: press the up or down arrow" data-sortable-handle="" data-only="script">⠿</span>Fallback 2 · node-c.example:18081</summary><div class="disclosure-body">The node's address, and whether it uses TLS.<div class="form-actions"><button class="mk-btn" type="button" data-only="no-script">Move up</button></div></div></details></li><li class="mk-sortable-item" data-sortable-fixed=""><details class="disclosure-row"><summary>+ Add a node</summary><div class="disclosure-body">An address to try last.</div></details></li></ol>

SortableItem

One item of a SortableList: usually a Disclosure with the Row look, its summary holding the item's SortHandle.

Props
PropTypeAbout
childrenSlotRequired.
fixedboolStays at the end, and isn't moved: a blank row to add one. Default: false.
restAttrs

SortHandle

What drags a SortableList's item, and takes its arrow keys. Shown only with JavaScript: without it the list's Move buttons reorder.

Props
PropTypeAbout
labelStringRequired. What moving it does, for screen readers ("Reorder Primary: press the up or down arrow").
restAttrs

Checks

What's done and what isn't, or what will happen: a list of rows each with a mark, which screen readers hear as "Done:" or "Not yet:".

Props
PropTypeAbout
checksVec<Check>Required. One row of [Checks].
restAttrs
Before removing a website
  • Done: No plugin is connected to it
  • Not yet: No order from shop.example.com can still be paid2 can be paid until about 14:20
  • Checkouts embedded on shop.example.com stop loading
Rust, Solid and HTML

Rust

Checks::new(vec![
    Check { mark: CheckMark::Ok, what: "No plugin is connected to it".into(), fix: None },
    Check { mark: CheckMark::No, what: "No order from shop.example.com can still be paid".into(), fix: Some("2 can be paid until about 14:20".into()) },
    Check { mark: CheckMark::Then, what: "Checkouts embedded on shop.example.com stop loading".into(), fix: None },
])

Solid

<Checks checks={[
  {
    "mark": "ok",
    "what": {
      "$slot": [
        "No plugin is connected to it"
      ]
    }
  },
  {
    "fix": {
      "$slot": [
        "2 can be paid until about 14:20"
      ]
    },
    "mark": "no",
    "what": {
      "$slot": [
        "No order from shop.example.com can still be paid"
      ]
    }
  },
  {
    "mark": "then",
    "what": {
      "$slot": [
        "Checkouts embedded on shop.example.com stop loading"
      ]
    }
  }
]} />

HTML

<ul class="checks"><li class="ok"><span class="mark" aria-hidden="true">✓</span><span><span class="visually-hidden">Done: </span>No plugin is connected to it</span></li><li class="no"><span class="mark" aria-hidden="true">✕</span><span><span class="visually-hidden">Not yet: </span>No order from shop.example.com can still be paid<span class="fix">2 can be paid until about 14:20</span></span></li><li class="then"><span class="mark" aria-hidden="true">→</span><span>Checkouts embedded on shop.example.com stop loading</span></li></ul>

Timeline

What happened to something, in order, each with when.

Props
PropTypeAbout
eventsVec<Event>Required. One event of a [Timeline].
A wallet's history
  1. Added
  2. Backup checked
  3. First payment
Rust, Solid and HTML

Rust

Timeline::new(vec![
    Event { when: "12 Oct, 14:02".into(), what: "Added".into() },
    Event { when: "12 Oct, 14:09".into(), what: "Backup checked".into() },
    Event { when: "13 Oct, 09:41".into(), what: "First payment".into() },
])

Solid

<Timeline events={[
  {
    "what": {
      "$slot": [
        "Added"
      ]
    },
    "when": "12 Oct, 14:02"
  },
  {
    "what": {
      "$slot": [
        "Backup checked"
      ]
    },
    "when": "12 Oct, 14:09"
  },
  {
    "what": {
      "$slot": [
        "First payment"
      ]
    },
    "when": "13 Oct, 09:41"
  }
]} />

HTML

<ol class="timeline"><li><time>12 Oct, 14:02</time><span>Added</span></li><li><time>12 Oct, 14:09</time><span>Backup checked</span></li><li><time>13 Oct, 09:41</time><span>First payment</span></li></ol>

Table

A table, in a box of its own that scrolls sideways when it's wider than the page. On a phone a column whose cells are optional goes, and look reshapes the rest. A page styling its columns names the table with data-table.

Props
PropTypeAbout
childrenSlotRequired. The rows: Rows (or a page's trs).
headOption<Slot>The header row.
lookTableLook (plain | cards | stack | facts)How a table changes on a phone. Default: plain.
captionOption<String>For screen readers: what the table is.
scrollboolWithout its scroll box: a table already narrow enough. Default: true.
restAttrs
Orders, cards on a phone
OrderStatusAmount
CoffeePaid0.02 XMR
TeaWaiting for payment0.01 XMR
Rust, Solid and HTML

Rust

Table::new(Slot::many([Row::new(Slot::many([Cell::new("Coffee")
    .part(CellPart::Title), Cell::new(Tag::new("Paid")
    .state(OrderState::Paid))
    .part(CellPart::Status), Cell::new("0.02 XMR")
    .part(CellPart::Amount)])), Row::new(Slot::many([Cell::new("Tea")
    .part(CellPart::Title), Cell::new(Tag::new("Waiting for payment")
    .state(OrderState::Pending)
    .short("Waiting"))
    .part(CellPart::Status), Cell::new("0.01 XMR")
    .part(CellPart::Amount)]))]))
    .head(Row::new(Slot::many([Cell::new("Order")
        .header(true), Cell::new("Status")
        .header(true), Cell::new("Amount")
        .header(true)])))
    .look(TableLook::Cards)

Solid

<Table head={{
  "$slot": [
    {
      "$component": "Row",
      "props": {
        "children": {
          "$slot": [
            {
              "$component": "Cell",
              "props": {
                "children": {
                  "$slot": [
                    "Order"
                  ]
                },
                "header": true,
                "optional": false
              }
            },
            {
              "$component": "Cell",
              "props": {
                "children": {
                  "$slot": [
                    "Status"
                  ]
                },
                "header": true,
                "optional": false
              }
            },
            {
              "$component": "Cell",
              "props": {
                "children": {
                  "$slot": [
                    "Amount"
                  ]
                },
                "header": true,
                "optional": false
              }
            }
          ]
        }
      }
    }
  ]
}} look="cards"><Row><Cell part="title">Coffee</Cell><Cell part="status"><Tag state="paid">Paid</Tag></Cell><Cell part="amount">0.02 XMR</Cell></Row><Row><Cell part="title">Tea</Cell><Cell part="status"><Tag state="pending" short="Waiting">Waiting for payment</Tag></Cell><Cell part="amount">0.01 XMR</Cell></Row></Table>

HTML

<div class="table-scroll"><table class="table-cards"><thead><tr><th>Order</th><th>Status</th><th>Amount</th></tr></thead><tbody><tr><td class="card-title">Coffee</td><td class="card-status"><span class="tag state-paid">Paid</span></td><td class="card-amount">0.02 XMR</td></tr><tr><td class="card-title">Tea</td><td class="card-status"><span class="tag state-pending"><span class="label-long">Waiting for payment</span><span class="label-short" aria-hidden="true">Waiting</span></span></td><td class="card-amount">0.01 XMR</td></tr></tbody></table></div>
Facts
NetworkMainnet
Height3,412,001
Rust, Solid and HTML

Rust

Table::new(Slot::many([Row::new(Slot::many([Cell::new("Network")
    .header(true), Cell::new("Mainnet")])), Row::new(Slot::many([Cell::new("Height")
    .header(true), Cell::new("3,412,001")]))]))
    .look(TableLook::Facts)

Solid

<Table look="facts"><Row><Cell header>Network</Cell><Cell>Mainnet</Cell></Row><Row><Cell header>Height</Cell><Cell>3,412,001</Cell></Row></Table>

HTML

<div class="table-scroll"><table class="kv-table"><tbody><tr><th>Network</th><td>Mainnet</td></tr><tr><th>Height</th><td>3,412,001</td></tr></tbody></table></div>

Pagination

Links to the pages before and after this one of a long list, and where this one is. Real links: with fixi_target, fixi swaps the next page into that element and keeps the address in step.

Props
PropTypeAbout
labelStringRequired. What is paged ("Orders pages").
previousOption<String>
nextOption<String>
previous_labelStringDefault: "Previous".
next_labelStringDefault: "Next".
statusOption<String>Where this page is ("Page 2 of 5").
fixi_targetOption<String>
restAttrs
Both ways, with where
Rust, Solid and HTML

Rust

Pagination::new("Invite pages")
    .previous("#component-pagination")
    .next("#component-pagination")
    .status("Page 2 of 5")

Solid

<Pagination label="Invite pages" previous="#component-pagination" next="#component-pagination" status="Page 2 of 5" />

HTML

<nav class="mk-pagination" aria-label="Invite pages"><a href="#component-pagination" rel="prev">← Previous</a><span class="mk-pagination-status">Page 2 of 5</span><a href="#component-pagination" rel="next">Next →</a></nav>
Newest first, at the start
Rust, Solid and HTML

Rust

Pagination::new("Orders pages")
    .next("#component-pagination")
    .next_label("Older")

Solid

<Pagination label="Orders pages" next="#component-pagination" next_label="Older" />

HTML

<nav class="mk-pagination" aria-label="Orders pages"><a href="#component-pagination" rel="next">Older →</a></nav>

Card

One thing in a list of them, as a card: a title and a line under it, its status at the right, a figure (an amount), and a foot with a detail and what to do with it. An order on the POS's list.

Props
PropTypeAbout
titleSlotRequired.
subtitleOption<Slot>
statusOption<Slot>At the head's right: a Tag.
amountOption<Slot>The figure: an amount.
detailOption<Slot>Muted, at the foot's left.
actionOption<Slot>At the foot's right: a link or a button.
restAttrs
An order

Table 4

Created 14:02

Waiting for payment

0.42 XMR

Expires in 52 minOpen
Rust, Solid and HTML

Rust

Card::new("Table 4")
    .subtitle("Created 14:02")
    .status(Tag::new("Waiting for payment")
        .state(OrderState::Pending)
        .short("Waiting"))
    .amount("0.42 XMR")
    .detail("Expires in 52 min")
    .action(LinkButton::new("Open", "#component-card")
        .variant(ButtonVariant::Quiet)
        .size(ControlSize::Compact))

Solid

<Card title={{
  "$slot": [
    "Table 4"
  ]
}} subtitle={{
  "$slot": [
    "Created 14:02"
  ]
}} status={{
  "$slot": [
    {
      "$component": "Tag",
      "props": {
        "children": {
          "$slot": [
            "Waiting for payment"
          ]
        },
        "short": "Waiting",
        "state": "pending"
      }
    }
  ]
}} amount={{
  "$slot": [
    "0.42 XMR"
  ]
}} detail={{
  "$slot": [
    "Expires in 52 min"
  ]
}} action={{
  "$slot": [
    {
      "$component": "LinkButton",
      "props": {
        "block": false,
        "children": {
          "$slot": [
            "Open"
          ]
        },
        "href": "#component-card",
        "size": "compact",
        "variant": "quiet"
      }
    }
  ]
}} />

HTML

<article class="mk-card"><div class="mk-card-head"><div><h3 class="mk-card-title">Table 4</h3><p class="mk-card-sub">Created 14:02</p></div><span class="tag state-pending"><span class="label-long">Waiting for payment</span><span class="label-short" aria-hidden="true">Waiting</span></span></div><p class="mk-card-amount">0.42 XMR</p><div class="mk-card-foot"><small>Expires in 52 min</small><a class="mk-btn" href="#component-card" data-variant="quiet" data-size="compact">Open</a></div></article>

Row

A table's row.

Props
PropTypeAbout
childrenSlotRequired.
restAttrs

Cell

A table's cell; a header cell with header.

Props
PropTypeAbout
childrenSlotRequired.
headerboolDefault: false.
partOption<CellPart (title | status | amount | when | meta | detail)>Where a cell goes when its row is a card on a phone.
labelOption<String>Its column's name, shown above it in a Stack table on a phone.
optionalboolLeft out on a phone. Default: false.
restAttrs

Dialog

A modal dialog, opened by the links and buttons naming it in data-opens-dialog. It gives focus to its first field, and back to what opened it once it closes.

Props
PropTypeAbout
idStringRequired.
labelled_byStringRequired. The id of its title (a DialogTitle).
childrenSlotRequired.
sizeDialogSize (confirm | wide)How wide a dialog is. Default: confirm.
confirmsOption<String>A form's field (its name) whose change the form's save asks about with this dialog first: the field holds what was saved in data-saved. The dialog's field marked data-confirmed gets what was typed.
confirms_whenConfirmWhen (changed | emptied)When a form's save opens a dialog instead (Dialog::confirms). Default: changed.
light_dismissboolA click on the backdrop closes it too. Default: false.
restAttrs
A question (the button opens it)
Retire wallet…

Retire this wallet?

Takes it out of every list and deletes its keys from Monokulo.

Rust, Solid and HTML

Rust

Dialog::new("book-dialog", "book-dialog-title", Slot::many([DialogTitle::new("book-dialog-title", "Retire this wallet?"), Hint::new("Takes it out of every list and deletes its keys from Monokulo."), DialogActions::new(Slot::many([Button::new("Cancel")
    .attr("data-closes-dialog", ""), Button::new("Retire wallet")
    .variant(ButtonVariant::Danger)]))]))

Solid

<Dialog id="book-dialog" labelled_by="book-dialog-title"><DialogTitle id="book-dialog-title">Retire this wallet?</DialogTitle><Hint>Takes it out of every list and deletes its keys from Monokulo.</Hint><DialogActions><Button data-closes-dialog="">Cancel</Button><Button variant="danger">Retire wallet</Button></DialogActions></Dialog>

HTML

<dialog id="book-dialog" class="settings-dialog confirm-dialog" aria-labelledby="book-dialog-title" data-mk="1" data-island="Dialog" data-props="{&quot;confirms_when&quot;:&quot;changed&quot;,&quot;id&quot;:&quot;book-dialog&quot;}"><h2 id="book-dialog-title" class="dialog-title">Retire this wallet?<button type="button" class="dialog-x" aria-label="Close" data-closes-dialog>×</button></h2><p class="hint">Takes it out of every list and deletes its keys from Monokulo.</p><div class="dialog-actions"><button class="mk-btn" type="button" data-closes-dialog="">Cancel</button><button class="mk-btn" type="button" data-variant="danger">Retire wallet</button></div></dialog>

DialogTitle

A dialog's title: an h2, with a ✕ that closes the dialog. On the content's own page, the title is the page's (PageHeader).

Props
PropTypeAbout
idStringRequired.
childrenSlotRequired.
asideOption<Slot>Beside the title, before the ✕: a status.
A question

Delete this webhook?

Rust, Solid and HTML

Rust

DialogTitle::new("book-title-dialog", "Delete this webhook?")

Solid

<DialogTitle id="book-title-dialog">Delete this webhook?</DialogTitle>

HTML

<h2 id="book-title-dialog" class="dialog-title">Delete this webhook?<button type="button" class="dialog-x" aria-label="Close" data-closes-dialog>×</button></h2>
With a status

Delivery of order.paidretrying

Rust, Solid and HTML

Rust

DialogTitle::new("book-title-status", "Delivery of order.paid")
    .aside(Tag::new("retrying")
        .tone(TagTone::Slow))

Solid

<DialogTitle id="book-title-status" aside={{
  "$slot": [
    {
      "$component": "Tag",
      "props": {
        "children": {
          "$slot": [
            "retrying"
          ]
        },
        "tone": "slow"
      }
    }
  ]
}}>Delivery of order.paid</DialogTitle>

HTML

<h2 id="book-title-status" class="dialog-title">Delivery of order.paid<span class="dialog-aside"><span class="tag tag-slow">retrying</span></span><button type="button" class="dialog-x" aria-label="Close" data-closes-dialog>×</button></h2>

DialogActions

A dialog's answers, at its foot on the right: Cancel, then the action.

Props
PropTypeAbout
childrenSlotRequired.
Cancel and an action
Cancel
Rust, Solid and HTML

Rust

DialogActions::new(Slot::many([LinkButton::new("Cancel", "#component-dialog-actions"), Button::new("Delete webhook")
    .variant(ButtonVariant::Danger)]))

Solid

<DialogActions><LinkButton href="#component-dialog-actions">Cancel</LinkButton><Button variant="danger">Delete webhook</Button></DialogActions>

HTML

<div class="dialog-actions"><a class="mk-btn" href="#component-dialog-actions">Cancel</a><button class="mk-btn" type="button" data-variant="danger">Delete webhook</button></div>

DialogPage

A dialog's content as a page of its own: where its opener goes without JavaScript. The page's header holds its title.

Props
PropTypeAbout
childrenSlotRequired.
sizeDialogSize (confirm | wide)How wide a dialog is. Default: confirm.
A question

Takes it out of every list and deletes its keys from Monokulo.

Cancel
Rust, Solid and HTML

Rust

DialogPage::new(Slot::many([Hint::new("Takes it out of every list and deletes its keys from Monokulo."), DialogActions::new(Slot::many([LinkButton::new("Cancel", "#component-dialog-page"), Button::new("Retire wallet")
    .variant(ButtonVariant::Danger)]))]))

Solid

<DialogPage><Hint>Takes it out of every list and deletes its keys from Monokulo.</Hint><DialogActions><LinkButton href="#component-dialog-page">Cancel</LinkButton><Button variant="danger">Retire wallet</Button></DialogActions></DialogPage>

HTML

<div class="confirm-dialog-page"><p class="hint">Takes it out of every list and deletes its keys from Monokulo.</p><div class="dialog-actions"><a class="mk-btn" href="#component-dialog-page">Cancel</a><button class="mk-btn" type="button" data-variant="danger">Retire wallet</button></div></div>

Field

A labelled text input, with what it's for and what's wrong with it tied to it for screen readers. The label is required: an input never goes without one.

Props
PropTypeAbout
labelStringRequired. What the input is ("Email").
idStringRequired. The input's id: the label, hint and error point at it.
nameStringRequired. The form field's name.
kindInputType (text | email | password | number | url | search | tel | datetime-local)The kind of text an input takes: phones show the matching keyboard. Default: text.
valueOption<String>
placeholderOption<String>
autocompleteOption<String>
hintOption<String>What it's for, under the input.
errorOption<String>What's wrong with what was entered. Marks the input invalid.
requiredboolDefault: false.
readonlyboolDefault: false.
disabledboolDefault: false.
sizeOption<ControlSize (compact | touch)>A control's size. Without one it is the normal size.
multilineboolSeveral lines of text (a message, a note): a text area, rows tall, instead of an input; kind doesn't apply. Default: false.
rowsnumberDefault: 4.
restAttrsMore attributes on the input: minlength, inputmode, fx-*.
Several lines

Who you are, and what you'll use Monokulo for.

Rust, Solid and HTML

Rust

Field::new("Message", "book-message", "message")
    .hint("Who you are, and what you'll use Monokulo for.")
    .required(true)
    .multiline(true)

Solid

<Field label="Message" id="book-message" name="message" hint="Who you are, and what you'll use Monokulo for." required multiline />

HTML

<div class="mk-field"><label for="book-message">Message</label><textarea class="mk-input" id="book-message" name="message" rows="4" required aria-describedby="book-message-help"></textarea><p class="mk-hint" id="book-message-help">Who you are, and what you'll use Monokulo for.</p></div>
With a hint

Shown to customers on the payment page.

Rust, Solid and HTML

Rust

Field::new("Store name", "book-store-name", "name")
    .placeholder("Your store")
    .hint("Shown to customers on the payment page.")

Solid

<Field label="Store name" id="book-store-name" name="name" placeholder="Your store" hint="Shown to customers on the payment page." />

HTML

<div class="mk-field"><label for="book-store-name">Store name</label><input class="mk-input" id="book-store-name" name="name" type="text" placeholder="Your store" aria-describedby="book-store-name-help"><p class="mk-hint" id="book-store-name-help">Shown to customers on the payment page.</p></div>
Refused

Enter a complete email address.

Rust, Solid and HTML

Rust

Field::new("Email", "book-email", "email")
    .kind(InputType::Email)
    .value("incomplete")
    .error("Enter a complete email address.")

Solid

<Field label="Email" id="book-email" name="email" kind="email" value="incomplete" error="Enter a complete email address." />

HTML

<div class="mk-field"><label for="book-email">Email</label><input class="mk-input" id="book-email" name="email" type="email" value="incomplete" aria-invalid="true" aria-describedby="book-email-error"><p class="mk-error" id="book-email-error">Enter a complete email address.</p></div>
Required, with input rules

At least 8 characters.

Rust, Solid and HTML

Rust

Field::new("Password", "book-password", "password")
    .kind(InputType::Password)
    .autocomplete("new-password")
    .hint("At least 8 characters.")
    .required(true)
    .attr("minlength", "8")

Solid

<Field label="Password" id="book-password" name="password" kind="password" autocomplete="new-password" hint="At least 8 characters." required minlength="8" />

HTML

<div class="mk-field"><label for="book-password">Password</label><input class="mk-input" id="book-password" name="password" type="password" autocomplete="new-password" required aria-describedby="book-password-help" minlength="8"><p class="mk-hint" id="book-password-help">At least 8 characters.</p></div>
Read only
Rust, Solid and HTML

Rust

Field::new("Store id", "book-readonly", "store")
    .value("Store 123")
    .readonly(true)

Solid

<Field label="Store id" id="book-readonly" name="store" value="Store 123" readonly />

HTML

<div class="mk-field"><label for="book-readonly">Store id</label><input class="mk-input" id="book-readonly" name="store" type="text" value="Store 123" readonly></div>
Disabled
Rust, Solid and HTML

Rust

Field::new("Instance URL", "book-disabled", "url")
    .kind(InputType::Url)
    .value("https://pay.example.com")
    .disabled(true)

Solid

<Field label="Instance URL" id="book-disabled" name="url" kind="url" value="https://pay.example.com" disabled />

HTML

<div class="mk-field"><label for="book-disabled">Instance URL</label><input class="mk-input" id="book-disabled" name="url" type="url" value="https://pay.example.com" disabled></div>
Compact
Rust, Solid and HTML

Rust

Field::new("Refund address", "book-compact", "refund")
    .size(ControlSize::Compact)

Solid

<Field label="Refund address" id="book-compact" name="refund" size="compact" />

HTML

<div class="mk-field"><label for="book-compact">Refund address</label><input class="mk-input" id="book-compact" name="refund" type="text" data-size="compact"></div>

Input

A text input on its own, for a control whose label is elsewhere: a Setting's, a table column's (label, read by screen readers). A labelled input with its hint and error is a Field.

Props
PropTypeAbout
nameStringRequired.
idOption<String>
kindInputType (text | email | password | number | url | search | tel | datetime-local)The kind of text an input takes: phones show the matching keyboard. Default: text.
valueOption<String>
placeholderOption<String>
autocompleteOption<String>
labelOption<String>Its name for screen readers, when nothing on the page labels it.
described_byOption<String>The ids of what describes it: its help, its problem.
invalidboolWhat was entered was refused. Default: false.
requiredboolDefault: false.
readonlyboolDefault: false.
disabledboolDefault: false.
sizeOption<ControlSize (compact | touch)>A control's size. Without one it is the normal size.
actionsOption<Slot>Inside its right edge: IconButtons (scan a code, choose a file). Their own names say what they do.
restAttrsMore attributes: minlength, inputmode, data-saved, fx-*.
A URL
Rust, Solid and HTML

Rust

Input::new("webhook_url")
    .kind(InputType::Url)
    .placeholder("https://shop.example/webhook")
    .label("Webhook URL")

Solid

<Input name="webhook_url" kind="url" placeholder="https://shop.example/webhook" label="Webhook URL" />

HTML

<input class="mk-input" type="url" name="webhook_url" placeholder="https://shop.example/webhook" aria-label="Webhook URL">
Refused
Rust, Solid and HTML

Rust

Input::new("confirmations")
    .kind(InputType::Number)
    .value("-1")
    .label("Confirmations")
    .invalid(true)

Solid

<Input name="confirmations" kind="number" value="-1" label="Confirmations" invalid />

HTML

<input class="mk-input" type="number" name="confirmations" value="-1" aria-label="Confirmations" aria-invalid="true">
Compact
Rust, Solid and HTML

Rust

Input::new("q")
    .kind(InputType::Search)
    .label("Search")
    .size(ControlSize::Compact)

Solid

<Input name="q" kind="search" label="Search" size="compact" />

HTML

<input class="mk-input" type="search" name="q" aria-label="Search" data-size="compact">
With actions inside
Rust, Solid and HTML

Rust

Input::new("refund_address")
    .placeholder("Your Monero refund address")
    .label("Refund address")
    .actions(Slot::many([IconButton::new("Scan refund QR", "⌗"), IconButton::new("Choose QR image", "▣")]))

Solid

<Input name="refund_address" placeholder="Your Monero refund address" label="Refund address" actions={{
  "$slot": [
    {
      "$component": "IconButton",
      "props": {
        "disabled": false,
        "icon": {
          "$slot": [
            "⌗"
          ]
        },
        "kind": "button",
        "label": "Scan refund QR"
      }
    },
    {
      "$component": "IconButton",
      "props": {
        "disabled": false,
        "icon": {
          "$slot": [
            "▣"
          ]
        },
        "kind": "button",
        "label": "Choose QR image"
      }
    }
  ]
}} />

HTML

<span class="mk-input-with-actions"><input class="mk-input" type="text" name="refund_address" placeholder="Your Monero refund address" aria-label="Refund address"><span class="mk-input-actions"><button type="button" class="mk-btn icon-only" aria-label="Scan refund QR" title="Scan refund QR">⌗</button><button type="button" class="mk-btn icon-only" aria-label="Choose QR image" title="Choose QR image">▣</button></span></span>

FieldNote

A note under a field: what it's for, or how what's in it stands. The control points at it with aria-describedby and its id.

Props
PropTypeAbout
childrenSlotRequired.
toneNoteTone (help | problem | pending | fine)What a note under a field says. Default: help.
idOption<String>
liveboolSaid as it changes (a check as you type): role="status". Default: false.
restAttrs
Help
The node's host and port, like node.example.com:18089.
Rust, Solid and HTML

Rust

FieldNote::new("The node's host and port, like node.example.com:18089.")

Solid

<FieldNote>The node's host and port, like node.example.com:18089.</FieldNote>

HTML

<span class="field-help">The node's host and port, like node.example.com:18089.</span>
A problem
Add the port, like node.example.com:18081.
Rust, Solid and HTML

Rust

FieldNote::new("Add the port, like node.example.com:18081.")
    .tone(NoteTone::Problem)

Solid

<FieldNote tone="problem">Add the port, like node.example.com:18081.</FieldNote>

HTML

<span class="setting-problem">Add the port, like node.example.com:18081.</span>
Pending
Saved - restart needed for it to take effect.
Rust, Solid and HTML

Rust

FieldNote::new("Saved - restart needed for it to take effect.")
    .tone(NoteTone::Pending)

Solid

<FieldNote tone="pending">Saved - restart needed for it to take effect.</FieldNote>

HTML

<span class="setting-pending">Saved - restart needed for it to take effect.</span>
Fine, said as it changes
✓ Free to use.
Rust, Solid and HTML

Rust

FieldNote::new("✓ Free to use.")
    .tone(NoteTone::Fine)
    .live(true)

Solid

<FieldNote tone="fine" live>✓ Free to use.</FieldNote>

HTML

<span class="field-check" role="status">✓ Free to use.</span>

LockedValue

A value the page shows but can't change (set on the command line, by a connected plugin): a disabled box with a padlock, and no name, so it is never sent with a form. A secret shows as dots, never its value.

Props
PropTypeAbout
valueStringRequired.
idOption<String>For a label for to name it.
labelled_byOption<String>What names it, without a label for.
described_byOption<String>
secretboolDefault: false.
Set elsewhere
Rust, Solid and HTML

Rust

LockedValue::new("https://pay.example.com")
    .id("book-locked")

Solid

<LockedValue value="https://pay.example.com" id="book-locked" />

HTML

<span class="locked-input"><input class="mk-input" type="text" value="https://pay.example.com" id="book-locked" disabled><svg class="lock-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M5 7V5a3 3 0 0 1 6 0v2" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path><rect x="3" y="7" width="10" height="7" rx="1.5" fill="currentColor"></rect></svg></span>
A secret
Rust, Solid and HTML

Rust

LockedValue::new("")
    .id("book-locked-secret")
    .secret(true)

Solid

<LockedValue value="" id="book-locked-secret" secret />

HTML

<span class="locked-input"><input class="mk-input" type="password" value="locked" id="book-locked-secret" disabled><svg class="lock-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M5 7V5a3 3 0 0 1 6 0v2" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path><rect x="3" y="7" width="10" height="7" rx="1.5" fill="currentColor"></rect></svg></span>

TextArea

Text over several lines. Its label is elsewhere, like an Input's.

Props
PropTypeAbout
nameStringRequired.
idOption<String>
valueOption<String>
rowsnumberDefault: 3.
placeholderOption<String>
labelOption<String>
described_byOption<String>
invalidboolDefault: false.
requiredboolDefault: false.
readonlyboolDefault: false.
disabledboolDefault: false.
restAttrs
A note
Rust, Solid and HTML

Rust

TextArea::new("note")
    .placeholder("For the customer's receipt")
    .label("Note")

Solid

<TextArea name="note" placeholder="For the customer's receipt" label="Note" />

HTML

<textarea class="mk-input" name="note" rows="3" placeholder="For the customer's receipt" aria-label="Note"></textarea>

Checkbox

A checkbox and what ticking it means, beside it. Without words beside it (a table's column says it), label names it.

Props
PropTypeAbout
textOption<Slot>What ticking it means: the label.
nameOption<String>
idOption<String>
valueStringWhat it sends when ticked. Default: "on".
checkedboolDefault: false.
labelOption<String>Its name for screen readers, without text.
described_byOption<String>
disabledboolDefault: false.
restAttrsMore attributes on the checkbox: data-saved, a script's hook.
With what it means
Rust, Solid and HTML

Rust

Checkbox::new()
    .text("I've written all 16 words down, in order, and put them somewhere safe.")

Solid

<Checkbox text={{
  "$slot": [
    "I've written all 16 words down, in order, and put them somewhere safe."
  ]
}} />

HTML

<label class="check-line"><input type="checkbox" value="on"><span>I've written all 16 words down, in order, and put them somewhere safe.</span></label>
Ticked
Rust, Solid and HTML

Rust

Checkbox::new()
    .text("Use TLS")
    .name("ssl")
    .checked(true)

Solid

<Checkbox text={{
  "$slot": [
    "Use TLS"
  ]
}} name="ssl" checked />

HTML

<label class="check-line"><input type="checkbox" name="ssl" value="on" checked><span>Use TLS</span></label>
In a table
Rust, Solid and HTML

Rust

Checkbox::new()
    .name("use_node")
    .label("Use node.example.com")

Solid

<Checkbox name="use_node" label="Use node.example.com" />

HTML

<input type="checkbox" class="mk-checkbox" name="use_node" value="on" aria-label="Use node.example.com">

Switch

On or off, said as a switch: a checkbox with role="switch", so it works and submits without JavaScript. An unticked checkbox sends nothing; listed_in names a field each switch adds its name to, so the form's handler can tell off from absent.

Props
PropTypeAbout
nameStringRequired.
idStringRequired.
onboolDefault: false.
described_byOption<String>
savedOption<Saved (on | off)>What is saved, when a refused save shows another value.
listed_inOption<String>
disabledboolDefault: false.
On
Rust, Solid and HTML

Rust

Switch::new("book.accept", "book-switch-on")
    .on(true)

Solid

<Switch name="book.accept" id="book-switch-on" on />

HTML

<label class="switch"><input type="checkbox" role="switch" name="book.accept" value="true" id="book-switch-on" checked><span class="switch-track" aria-hidden="true"></span><span class="switch-on" aria-hidden="true">On</span><span class="switch-off" aria-hidden="true">Off</span></label>
Off
Rust, Solid and HTML

Rust

Switch::new("book.notify", "book-switch-off")

Solid

<Switch name="book.notify" id="book-switch-off" />

HTML

<label class="switch"><input type="checkbox" role="switch" name="book.notify" value="true" id="book-switch-off"><span class="switch-track" aria-hidden="true"></span><span class="switch-on" aria-hidden="true">On</span><span class="switch-off" aria-hidden="true">Off</span></label>
Disabled
Rust, Solid and HTML

Rust

Switch::new("book.locked", "book-switch-disabled")
    .disabled(true)

Solid

<Switch name="book.locked" id="book-switch-disabled" disabled />

HTML

<label class="switch"><input type="checkbox" role="switch" name="book.locked" value="true" id="book-switch-disabled" disabled><span class="switch-track" aria-hidden="true"></span><span class="switch-on" aria-hidden="true">On</span><span class="switch-off" aria-hidden="true">Off</span></label>

RadioCards

One of a few choices, each a card: a radio button, maybe a picture, and its label. Its label is elsewhere (a Setting group's).

Props
PropTypeAbout
nameStringRequired.
cardsVec<RadioCard>Required. One choice of [RadioCards].
compactboolSmall cards, side by side: the theme's three. Default: false.
A theme
Rust, Solid and HTML

Rust

RadioCards::new("book_theme", vec![
    RadioCard { value: "system".into(), label: "System".into(), picture: None, checked: true, saved: None },
    RadioCard { value: "light".into(), label: "Light".into(), picture: None, checked: false, saved: None },
    RadioCard { value: "dark".into(), label: "Dark".into(), picture: None, checked: false, saved: None },
])
    .compact(true)

Solid

<RadioCards name="book_theme" cards={[
  {
    "checked": true,
    "label": {
      "$slot": [
        "System"
      ]
    },
    "value": "system"
  },
  {
    "checked": false,
    "label": {
      "$slot": [
        "Light"
      ]
    },
    "value": "light"
  },
  {
    "checked": false,
    "label": {
      "$slot": [
        "Dark"
      ]
    },
    "value": "dark"
  }
]} compact />

HTML

<div class="radio-cards is-compact"><label class="radio-card"><input type="radio" name="book_theme" value="system" checked>System</label><label class="radio-card"><input type="radio" name="book_theme" value="light">Light</label><label class="radio-card"><input type="radio" name="book_theme" value="dark">Dark</label></div>

IconButton

A button showing only an icon: its name, required, is what screen readers say and what hovering shows.

Props
PropTypeAbout
labelStringRequired. What it does ("Send again").
iconSlotRequired. The icon: an inline SVG.
kindButtonType (button | submit | reset)What a button does in its form. Default: button.
disabledboolDefault: false.
restAttrs
Send again
Rust, Solid and HTML

Rust

IconButton::new("Send again", "↻")

Solid

<IconButton label="Send again" icon={{
  "$slot": [
    "↻"
  ]
}} />

HTML

<button type="button" class="mk-btn icon-only" aria-label="Send again" title="Send again">↻</button>

FormActions

A form's buttons in a row; a spacer pushes the ones after it to the right.

Props
PropTypeAbout
childrenSlotRequired.
restAttrs
Save and Cancel
Cancel
Rust, Solid and HTML

Rust

FormActions::new(Slot::many([Button::new("Add webhook")
    .variant(ButtonVariant::Primary)
    .kind(ButtonType::Submit), LinkButton::new("Cancel", "#component-form-actions")]))

Solid

<FormActions><Button variant="primary" kind="submit">Add webhook</Button><LinkButton href="#component-form-actions">Cancel</LinkButton></FormActions>

HTML

<div class="form-actions"><button class="mk-btn" type="submit" data-variant="primary">Add webhook</button><a class="mk-btn" href="#component-form-actions">Cancel</a></div>

Select

The site's one dropdown. The page sends an ordinary select: without JavaScript it is all there is, and each option's text says everything. Once its island attaches, a button and a list that floats over the page draw the options' parts, with search, type-ahead and the keys a listbox takes; the select stays the field, so forms, required, fixi and other scripts that read it keep working.

Props
PropTypeAbout
nameStringRequired. The form field's name.
optionsVec<Choice>Required. One option of a [Select]. Its text says everything in words, for a browser without JavaScript (Label (detail) [Network] [chip] - note); the enhanced list draws the same parts on their own: a logo, a label, a monospace detail, a network badge, a chip, a note.
idOption<String>
labelOption<String>The accessible name, for a select with no <label>.
compactboolThe toolbar size. Default: false.
searchSearchMode (auto | show | hide)Whether the list has a find box. Default: auto.
requiredboolDefault: false.
disabledboolDefault: false.
restAttrsMore attributes on the select: aria-describedby, data-saved, fx-*.
Currency, with a find box from 12 options
Rust, Solid and HTML

Rust

Select::new("currency", vec![
    Choice::new("AUD", "Australian Dollar").detail("AUD"),
    Choice::new("CAD", "Canadian Dollar").detail("CAD"),
    Choice::new("CHF", "Swiss Franc").detail("CHF"),
    Choice::new("EUR", "Euro").detail("EUR").selected(true),
    Choice::new("GBP", "Pound Sterling").detail("GBP"),
    Choice::new("JPY", "Japanese Yen").detail("JPY"),
    Choice::new("NZD", "New Zealand Dollar").detail("NZD"),
    Choice::new("SEK", "Swedish Krona").detail("SEK"),
    Choice::new("USD", "United States Dollar").detail("USD"),
    Choice::new("XMR", "Monero").detail("XMR"),
    Choice::new("BRL", "Brazilian Real").detail("BRL"),
    Choice::new("INR", "Indian Rupee").detail("INR"),
])
    .id("book-currency")
    .label("Base currency")

Solid

<Select name="currency" options={[
  {
    "current": false,
    "data_label": "Australian Dollar",
    "detail": "AUD",
    "disabled": false,
    "label": "Australian Dollar",
    "selected": false,
    "text": "Australian Dollar (AUD)",
    "value": "AUD"
  },
  {
    "current": false,
    "data_label": "Canadian Dollar",
    "detail": "CAD",
    "disabled": false,
    "label": "Canadian Dollar",
    "selected": false,
    "text": "Canadian Dollar (CAD)",
    "value": "CAD"
  },
  {
    "current": false,
    "data_label": "Swiss Franc",
    "detail": "CHF",
    "disabled": false,
    "label": "Swiss Franc",
    "selected": false,
    "text": "Swiss Franc (CHF)",
    "value": "CHF"
  },
  {
    "current": false,
    "data_label": "Euro",
    "detail": "EUR",
    "disabled": false,
    "label": "Euro",
    "selected": true,
    "text": "Euro (EUR)",
    "value": "EUR"
  },
  {
    "current": false,
    "data_label": "Pound Sterling",
    "detail": "GBP",
    "disabled": false,
    "label": "Pound Sterling",
    "selected": false,
    "text": "Pound Sterling (GBP)",
    "value": "GBP"
  },
  {
    "current": false,
    "data_label": "Japanese Yen",
    "detail": "JPY",
    "disabled": false,
    "label": "Japanese Yen",
    "selected": false,
    "text": "Japanese Yen (JPY)",
    "value": "JPY"
  },
  {
    "current": false,
    "data_label": "New Zealand Dollar",
    "detail": "NZD",
    "disabled": false,
    "label": "New Zealand Dollar",
    "selected": false,
    "text": "New Zealand Dollar (NZD)",
    "value": "NZD"
  },
  {
    "current": false,
    "data_label": "Swedish Krona",
    "detail": "SEK",
    "disabled": false,
    "label": "Swedish Krona",
    "selected": false,
    "text": "Swedish Krona (SEK)",
    "value": "SEK"
  },
  {
    "current": false,
    "data_label": "United States Dollar",
    "detail": "USD",
    "disabled": false,
    "label": "United States Dollar",
    "selected": false,
    "text": "United States Dollar (USD)",
    "value": "USD"
  },
  {
    "current": false,
    "data_label": "Monero",
    "detail": "XMR",
    "disabled": false,
    "label": "Monero",
    "selected": false,
    "text": "Monero (XMR)",
    "value": "XMR"
  },
  {
    "current": false,
    "data_label": "Brazilian Real",
    "detail": "BRL",
    "disabled": false,
    "label": "Brazilian Real",
    "selected": false,
    "text": "Brazilian Real (BRL)",
    "value": "BRL"
  },
  {
    "current": false,
    "data_label": "Indian Rupee",
    "detail": "INR",
    "disabled": false,
    "label": "Indian Rupee",
    "selected": false,
    "text": "Indian Rupee (INR)",
    "value": "INR"
  }
]} id="book-currency" label="Base currency" />

HTML

<div class="mk-select" data-mk="1" data-island="Select" data-props="{&quot;label&quot;:&quot;Base currency&quot;,&quot;search&quot;:&quot;auto&quot;}"><select class="mk-input" name="currency" id="book-currency" aria-label="Base currency" data-mk="2"><option value="AUD" data-label="Australian Dollar" data-detail="AUD">Australian Dollar (AUD)</option><option value="CAD" data-label="Canadian Dollar" data-detail="CAD">Canadian Dollar (CAD)</option><option value="CHF" data-label="Swiss Franc" data-detail="CHF">Swiss Franc (CHF)</option><option value="EUR" selected data-label="Euro" data-detail="EUR">Euro (EUR)</option><option value="GBP" data-label="Pound Sterling" data-detail="GBP">Pound Sterling (GBP)</option><option value="JPY" data-label="Japanese Yen" data-detail="JPY">Japanese Yen (JPY)</option><option value="NZD" data-label="New Zealand Dollar" data-detail="NZD">New Zealand Dollar (NZD)</option><option value="SEK" data-label="Swedish Krona" data-detail="SEK">Swedish Krona (SEK)</option><option value="USD" data-label="United States Dollar" data-detail="USD">United States Dollar (USD)</option><option value="XMR" data-label="Monero" data-detail="XMR">Monero (XMR)</option><option value="BRL" data-label="Brazilian Real" data-detail="BRL">Brazilian Real (BRL)</option><option value="INR" data-label="Indian Rupee" data-detail="INR">Indian Rupee (INR)</option></select><!--mk:3--></div>
Wallets, with networks, the current one and notes
Rust, Solid and HTML

Rust

Select::new("wallet_id", vec![
    Choice::new("", "Choose a wallet…").disabled(true),
    Choice::new("w_1", "Copper Heron").detail("48xQ7…v3Rk").network("mainnet").chip("Current").current(true).note("2 stores").selected(true),
    Choice::new("w_2", "Feather test").detail("5Bq2…9TtM").network("stagenet").note("1 store"),
    Choice::new("w_3", "Old till").detail("47aa…0QeN").network("mainnet").note("retired").disabled(true),
])
    .id("book-wallet")
    .label("Wallet")
    .required(true)

Solid

<Select name="wallet_id" options={[
  {
    "current": false,
    "disabled": true,
    "label": "Choose a wallet…",
    "selected": false,
    "text": "Choose a wallet…",
    "value": ""
  },
  {
    "chip": "Current",
    "current": true,
    "data_label": "Copper Heron",
    "detail": "48xQ7…v3Rk",
    "disabled": false,
    "label": "Copper Heron",
    "network": "mainnet",
    "note": "2 stores",
    "selected": true,
    "text": "Copper Heron (48xQ7…v3Rk) [Mainnet] [Current] - 2 stores",
    "value": "w_1"
  },
  {
    "current": false,
    "data_label": "Feather test",
    "detail": "5Bq2…9TtM",
    "disabled": false,
    "label": "Feather test",
    "network": "stagenet",
    "note": "1 store",
    "selected": false,
    "text": "Feather test (5Bq2…9TtM) [Stagenet] - 1 store",
    "value": "w_2"
  },
  {
    "current": false,
    "data_label": "Old till",
    "detail": "47aa…0QeN",
    "disabled": true,
    "label": "Old till",
    "network": "mainnet",
    "note": "retired",
    "selected": false,
    "text": "Old till (47aa…0QeN) [Mainnet] - retired",
    "value": "w_3"
  }
]} id="book-wallet" label="Wallet" required />

HTML

<div class="mk-select" data-mk="1" data-island="Select" data-props="{&quot;label&quot;:&quot;Wallet&quot;,&quot;search&quot;:&quot;auto&quot;}"><select class="mk-input" name="wallet_id" id="book-wallet" aria-label="Wallet" required data-mk="2"><option value="" disabled>Choose a wallet…</option><option value="w_1" selected data-label="Copper Heron" data-detail="48xQ7…v3Rk" data-network="mainnet" data-chip="Current" data-chip-tone="current" data-note="2 stores">Copper Heron (48xQ7…v3Rk) [Mainnet] [Current] - 2 stores</option><option value="w_2" data-label="Feather test" data-detail="5Bq2…9TtM" data-network="stagenet" data-note="1 store">Feather test (5Bq2…9TtM) [Stagenet] - 1 store</option><option value="w_3" disabled data-label="Old till" data-detail="47aa…0QeN" data-network="mainnet" data-note="retired">Old till (47aa…0QeN) [Mainnet] - retired</option></select><!--mk:3--></div>
A network on its own: the badge
Rust, Solid and HTML

Rust

Select::new("network", vec![
    Choice::new("mainnet", "").network("mainnet"),
    Choice::new("stagenet", "").network("stagenet").selected(true),
    Choice::new("testnet", "").network("testnet"),
])
    .id("book-network")
    .label("Network")

Solid

<Select name="network" options={[
  {
    "current": false,
    "data_label": "",
    "disabled": false,
    "label": "",
    "network": "mainnet",
    "selected": false,
    "text": "[Mainnet]",
    "value": "mainnet"
  },
  {
    "current": false,
    "data_label": "",
    "disabled": false,
    "label": "",
    "network": "stagenet",
    "selected": true,
    "text": "[Stagenet]",
    "value": "stagenet"
  },
  {
    "current": false,
    "data_label": "",
    "disabled": false,
    "label": "",
    "network": "testnet",
    "selected": false,
    "text": "[Testnet]",
    "value": "testnet"
  }
]} id="book-network" label="Network" />

HTML

<div class="mk-select" data-mk="1" data-island="Select" data-props="{&quot;label&quot;:&quot;Network&quot;,&quot;search&quot;:&quot;auto&quot;}"><select class="mk-input" name="network" id="book-network" aria-label="Network" data-mk="2"><option value="mainnet" data-label="" data-network="mainnet">[Mainnet]</option><option value="stagenet" selected data-label="" data-network="stagenet">[Stagenet]</option><option value="testnet" data-label="" data-network="testnet">[Testnet]</option></select><!--mk:3--></div>
Compact, for a toolbar
Rust, Solid and HTML

Rust

Select::new("level", vec![
    Choice::new("error", "Errors"),
    Choice::new("warn", "Warnings and up").selected(true),
    Choice::new("info", "Info and up"),
    Choice::new("debug", "Everything"),
])
    .id("book-level")
    .label("Level")
    .compact(true)

Solid

<Select name="level" options={[
  {
    "current": false,
    "disabled": false,
    "label": "Errors",
    "selected": false,
    "text": "Errors",
    "value": "error"
  },
  {
    "current": false,
    "disabled": false,
    "label": "Warnings and up",
    "selected": true,
    "text": "Warnings and up",
    "value": "warn"
  },
  {
    "current": false,
    "disabled": false,
    "label": "Info and up",
    "selected": false,
    "text": "Info and up",
    "value": "info"
  },
  {
    "current": false,
    "disabled": false,
    "label": "Everything",
    "selected": false,
    "text": "Everything",
    "value": "debug"
  }
]} id="book-level" label="Level" compact />

HTML

<div class="mk-select" data-compact data-mk="1" data-island="Select" data-props="{&quot;label&quot;:&quot;Level&quot;,&quot;search&quot;:&quot;auto&quot;}"><select class="mk-input" data-size="compact" name="level" id="book-level" aria-label="Level" data-mk="2"><option value="error">Errors</option><option value="warn" selected>Warnings and up</option><option value="info">Info and up</option><option value="debug">Everything</option></select><!--mk:3--></div>
Disabled
Rust, Solid and HTML

Rust

Select::new("language", vec![
    Choice::new("en", "English").selected(true),
])
    .id("book-language")
    .label("Language")
    .disabled(true)

Solid

<Select name="language" options={[
  {
    "current": false,
    "disabled": false,
    "label": "English",
    "selected": true,
    "text": "English",
    "value": "en"
  }
]} id="book-language" label="Language" disabled />

HTML

<div class="mk-select" data-mk="1" data-island="Select" data-props="{&quot;label&quot;:&quot;Language&quot;,&quot;search&quot;:&quot;auto&quot;}"><select class="mk-input" name="language" id="book-language" aria-label="Language" disabled data-mk="2"><option value="en" selected>English</option></select><!--mk:3--></div>

WizardSteps

Progress through a flow (setup: Store, Wallet, Done), as flat segments, each with a bar counting its tasks done. It shows where you are; it isn't navigation.

Props
PropTypeAbout
labelStringRequired. What the steps are of ("Setup progress").
stepsVec<WizardStep>Required. One segment of a wizard's strip.
Setup, backing up a new wallet
  1. StoreComplete
  2. WalletBack up · step 2 of 3
  3. DoneNext
Rust, Solid and HTML

Rust

WizardSteps::new("Setup progress", vec![
    WizardStep { label: "Store".into(), detail: "Complete".into(), done: 1, tasks: 1, state: WizardState::Done },
    WizardStep { label: "Wallet".into(), detail: "Back up · step 2 of 3".into(), done: 1, tasks: 3, state: WizardState::Current },
    WizardStep { label: "Done".into(), detail: "Next".into(), done: 0, tasks: 1, state: WizardState::Next },
])

Solid

<WizardSteps label="Setup progress" steps={[
  {
    "detail": "Complete",
    "done": 1,
    "label": "Store",
    "state": "done",
    "tasks": 1
  },
  {
    "detail": "Back up · step 2 of 3",
    "done": 1,
    "label": "Wallet",
    "state": "current",
    "tasks": 3
  },
  {
    "detail": "Next",
    "done": 0,
    "label": "Done",
    "state": "next",
    "tasks": 1
  }
]} />

HTML

<ol class="mk-wizard-steps" data-design="strip" aria-label="Setup progress"><li class="done"><span>Store<small>Complete</small></span><progress class="mk-wizard-progress" value="1" max="1" aria-label="Store: 1 of 1 tasks complete"></progress></li><li aria-current="step"><span>Wallet<small>Back up · step 2 of 3</small></span><progress class="mk-wizard-progress" value="1" max="3" aria-label="Wallet: 1 of 3 tasks complete"></progress></li><li><span>Done<small>Next</small></span><progress class="mk-wizard-progress" value="0" max="1" aria-label="Done: 0 of 1 tasks complete"></progress></li></ol>
Setup, bringing your own wallet
  1. StoreComplete
  2. WalletKeys · step 2 of 2
  3. DoneNext
Rust, Solid and HTML

Rust

WizardSteps::new("Setup progress", vec![
    WizardStep { label: "Store".into(), detail: "Complete".into(), done: 1, tasks: 1, state: WizardState::Done },
    WizardStep { label: "Wallet".into(), detail: "Keys · step 2 of 2".into(), done: 1, tasks: 2, state: WizardState::Current },
    WizardStep { label: "Done".into(), detail: "Next".into(), done: 0, tasks: 1, state: WizardState::Next },
])

Solid

<WizardSteps label="Setup progress" steps={[
  {
    "detail": "Complete",
    "done": 1,
    "label": "Store",
    "state": "done",
    "tasks": 1
  },
  {
    "detail": "Keys · step 2 of 2",
    "done": 1,
    "label": "Wallet",
    "state": "current",
    "tasks": 2
  },
  {
    "detail": "Next",
    "done": 0,
    "label": "Done",
    "state": "next",
    "tasks": 1
  }
]} />

HTML

<ol class="mk-wizard-steps" data-design="strip" aria-label="Setup progress"><li class="done"><span>Store<small>Complete</small></span><progress class="mk-wizard-progress" value="1" max="1" aria-label="Store: 1 of 1 tasks complete"></progress></li><li aria-current="step"><span>Wallet<small>Keys · step 2 of 2</small></span><progress class="mk-wizard-progress" value="1" max="2" aria-label="Wallet: 1 of 2 tasks complete"></progress></li><li><span>Done<small>Next</small></span><progress class="mk-wizard-progress" value="0" max="1" aria-label="Done: 0 of 1 tasks complete"></progress></li></ol>
Setup, checking the backup
  1. StoreComplete
  2. WalletCheck · step 3 of 3
  3. DoneNext
Rust, Solid and HTML

Rust

WizardSteps::new("Setup progress", vec![
    WizardStep { label: "Store".into(), detail: "Complete".into(), done: 1, tasks: 1, state: WizardState::Done },
    WizardStep { label: "Wallet".into(), detail: "Check · step 3 of 3".into(), done: 2, tasks: 3, state: WizardState::Current },
    WizardStep { label: "Done".into(), detail: "Next".into(), done: 0, tasks: 1, state: WizardState::Next },
])

Solid

<WizardSteps label="Setup progress" steps={[
  {
    "detail": "Complete",
    "done": 1,
    "label": "Store",
    "state": "done",
    "tasks": 1
  },
  {
    "detail": "Check · step 3 of 3",
    "done": 2,
    "label": "Wallet",
    "state": "current",
    "tasks": 3
  },
  {
    "detail": "Next",
    "done": 0,
    "label": "Done",
    "state": "next",
    "tasks": 1
  }
]} />

HTML

<ol class="mk-wizard-steps" data-design="strip" aria-label="Setup progress"><li class="done"><span>Store<small>Complete</small></span><progress class="mk-wizard-progress" value="1" max="1" aria-label="Store: 1 of 1 tasks complete"></progress></li><li aria-current="step"><span>Wallet<small>Check · step 3 of 3</small></span><progress class="mk-wizard-progress" value="2" max="3" aria-label="Wallet: 2 of 3 tasks complete"></progress></li><li><span>Done<small>Next</small></span><progress class="mk-wizard-progress" value="0" max="1" aria-label="Done: 0 of 1 tasks complete"></progress></li></ol>
Setup, finished
  1. StoreComplete
  2. WalletComplete
  3. DoneComplete
Rust, Solid and HTML

Rust

WizardSteps::new("Setup progress", vec![
    WizardStep { label: "Store".into(), detail: "Complete".into(), done: 1, tasks: 1, state: WizardState::Done },
    WizardStep { label: "Wallet".into(), detail: "Complete".into(), done: 1, tasks: 1, state: WizardState::Done },
    WizardStep { label: "Done".into(), detail: "Complete".into(), done: 1, tasks: 1, state: WizardState::Current },
])

Solid

<WizardSteps label="Setup progress" steps={[
  {
    "detail": "Complete",
    "done": 1,
    "label": "Store",
    "state": "done",
    "tasks": 1
  },
  {
    "detail": "Complete",
    "done": 1,
    "label": "Wallet",
    "state": "done",
    "tasks": 1
  },
  {
    "detail": "Complete",
    "done": 1,
    "label": "Done",
    "state": "current",
    "tasks": 1
  }
]} />

HTML

<ol class="mk-wizard-steps" data-design="strip" aria-label="Setup progress"><li class="done"><span>Store<small>Complete</small></span><progress class="mk-wizard-progress" value="1" max="1" aria-label="Store: 1 of 1 tasks complete"></progress></li><li class="done"><span>Wallet<small>Complete</small></span><progress class="mk-wizard-progress" value="1" max="1" aria-label="Wallet: 1 of 1 tasks complete"></progress></li><li aria-current="step"><span>Done<small>Complete</small></span><progress class="mk-wizard-progress" value="1" max="1" aria-label="Done: 1 of 1 tasks complete"></progress></li></ol>
Adding a wallet on its own
  1. WalletKeys · step 2 of 2
Rust, Solid and HTML

Rust

WizardSteps::new("Wallet progress", vec![
    WizardStep { label: "Wallet".into(), detail: "Keys · step 2 of 2".into(), done: 1, tasks: 2, state: WizardState::Current },
])

Solid

<WizardSteps label="Wallet progress" steps={[
  {
    "detail": "Keys · step 2 of 2",
    "done": 1,
    "label": "Wallet",
    "state": "current",
    "tasks": 2
  }
]} />

HTML

<ol class="mk-wizard-steps" data-design="strip" aria-label="Wallet progress"><li aria-current="step"><span>Wallet<small>Keys · step 2 of 2</small></span><progress class="mk-wizard-progress" value="1" max="2" aria-label="Wallet: 1 of 2 tasks complete"></progress></li></ol>

ProgressSteps

Where something is in a short, fixed sequence (a payment: Send, Confirm, Paid), with a line saying what that means and what to do: the same place and shape in every state, so nothing moves as it changes. The colours follow state; the words say it all.

Props
PropTypeAbout
labelStringRequired. What the steps are of ("Payment progress").
stepsVec<ProgressStep>Required. One step of [ProgressSteps].
titleStringRequired. The headline of the line under the steps ("Waiting for payment").
messageSlotRequired. The rest of the line.
stateOption<OrderState (pending | unconfirmed | confirming | partial | paid | overpaid | expired | cancelled | double-spend | offline | unknown)>The colours: an order's state.
compactboolThe POS's size: smaller type, the same shape. Default: false.
restAttrs
Waiting for a payment
  1. Send, now
  2. Confirm, to come
  3. Paid, to come

Send 0.42 XMR. Scan the code or copy the address. 58 min left

Rust, Solid and HTML

Rust

ProgressSteps::new("Payment progress", vec![
    ProgressStep { label: "Send".into(), progress: StepProgress::Now },
    ProgressStep { label: "Confirm".into(), progress: StepProgress::Todo },
    ProgressStep { label: "Paid".into(), progress: StepProgress::Todo },
], "Send 0.42 XMR", "Scan the code or copy the address. 58 min left")
    .state(OrderState::Pending)

Solid

<ProgressSteps label="Payment progress" steps={[
  {
    "label": "Send",
    "progress": "now"
  },
  {
    "label": "Confirm",
    "progress": "todo"
  },
  {
    "label": "Paid",
    "progress": "todo"
  }
]} title="Send 0.42 XMR" message={{
  "$slot": [
    "Scan the code or copy the address. 58 min left"
  ]
}} state="pending" />

HTML

<div class="mk-stage state-pending"><ol class="mk-track" aria-label="Payment progress"><li class="step-now"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Send<span class="visually-hidden">, now</span></span></li><li class="step-todo"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Confirm<span class="visually-hidden">, to come</span></span></li><li class="step-todo"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Paid<span class="visually-hidden">, to come</span></span></li></ol><p class="mk-stage-msg" role="status"><strong>Send 0.42 XMR.</strong> Scan the code or copy the address. 58 min left</p></div>
Part paid
  1. Send, partly done
  2. Confirm, to come
  3. Paid, to come

Part paid. 0.2 XMR in; send the other 0.22 XMR.

Rust, Solid and HTML

Rust

ProgressSteps::new("Payment progress", vec![
    ProgressStep { label: "Send".into(), progress: StepProgress::Part },
    ProgressStep { label: "Confirm".into(), progress: StepProgress::Todo },
    ProgressStep { label: "Paid".into(), progress: StepProgress::Todo },
], "Part paid", "0.2 XMR in; send the other 0.22 XMR.")
    .state(OrderState::Partial)

Solid

<ProgressSteps label="Payment progress" steps={[
  {
    "label": "Send",
    "progress": "part"
  },
  {
    "label": "Confirm",
    "progress": "todo"
  },
  {
    "label": "Paid",
    "progress": "todo"
  }
]} title="Part paid" message={{
  "$slot": [
    "0.2 XMR in; send the other 0.22 XMR."
  ]
}} state="partial" />

HTML

<div class="mk-stage state-partial"><ol class="mk-track" aria-label="Payment progress"><li class="step-part"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Send<span class="visually-hidden">, partly done</span></span></li><li class="step-todo"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Confirm<span class="visually-hidden">, to come</span></span></li><li class="step-todo"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Paid<span class="visually-hidden">, to come</span></span></li></ol><p class="mk-stage-msg" role="status"><strong>Part paid.</strong> 0.2 XMR in; send the other 0.22 XMR.</p></div>
Confirming
  1. Send, done
  2. Confirm, now
  3. Paid, to come

Confirming. Payment seen; 3 of 10 confirmations.

Rust, Solid and HTML

Rust

ProgressSteps::new("Payment progress", vec![
    ProgressStep { label: "Send".into(), progress: StepProgress::Done },
    ProgressStep { label: "Confirm".into(), progress: StepProgress::Now },
    ProgressStep { label: "Paid".into(), progress: StepProgress::Todo },
], "Confirming", "Payment seen; 3 of 10 confirmations.")
    .state(OrderState::Confirming)

Solid

<ProgressSteps label="Payment progress" steps={[
  {
    "label": "Send",
    "progress": "done"
  },
  {
    "label": "Confirm",
    "progress": "now"
  },
  {
    "label": "Paid",
    "progress": "todo"
  }
]} title="Confirming" message={{
  "$slot": [
    "Payment seen; 3 of 10 confirmations."
  ]
}} state="confirming" />

HTML

<div class="mk-stage state-confirming"><ol class="mk-track" aria-label="Payment progress"><li class="step-done"><span class="mk-track-dot" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m4.5 12.5 5 5 10-11"></path></svg></span><span class="mk-track-name">Send<span class="visually-hidden">, done</span></span></li><li class="step-now"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Confirm<span class="visually-hidden">, now</span></span></li><li class="step-todo"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Paid<span class="visually-hidden">, to come</span></span></li></ol><p class="mk-stage-msg" role="status"><strong>Confirming.</strong> Payment seen; 3 of 10 confirmations.</p></div>
Paid, counted as soon as seen
  1. Send, done
  2. Paid, done

Paid. Thank you.

Rust, Solid and HTML

Rust

ProgressSteps::new("Payment progress", vec![
    ProgressStep { label: "Send".into(), progress: StepProgress::Done },
    ProgressStep { label: "Paid".into(), progress: StepProgress::Done },
], "Paid", "Thank you.")
    .state(OrderState::Paid)

Solid

<ProgressSteps label="Payment progress" steps={[
  {
    "label": "Send",
    "progress": "done"
  },
  {
    "label": "Paid",
    "progress": "done"
  }
]} title="Paid" message={{
  "$slot": [
    "Thank you."
  ]
}} state="paid" />

HTML

<div class="mk-stage state-paid"><ol class="mk-track" aria-label="Payment progress"><li class="step-done"><span class="mk-track-dot" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m4.5 12.5 5 5 10-11"></path></svg></span><span class="mk-track-name">Send<span class="visually-hidden">, done</span></span></li><li class="step-done"><span class="mk-track-dot" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m4.5 12.5 5 5 10-11"></path></svg></span><span class="mk-track-name">Paid<span class="visually-hidden">, done</span></span></li></ol><p class="mk-stage-msg" role="status"><strong>Paid.</strong> Thank you.</p></div>
Expired
  1. Send, stopped
  2. Confirm, to come
  3. Paid, to come

Expired. Nothing arrived in time; ask for a new order.

Rust, Solid and HTML

Rust

ProgressSteps::new("Payment progress", vec![
    ProgressStep { label: "Send".into(), progress: StepProgress::Fail },
    ProgressStep { label: "Confirm".into(), progress: StepProgress::Todo },
    ProgressStep { label: "Paid".into(), progress: StepProgress::Todo },
], "Expired", "Nothing arrived in time; ask for a new order.")
    .state(OrderState::Expired)

Solid

<ProgressSteps label="Payment progress" steps={[
  {
    "label": "Send",
    "progress": "fail"
  },
  {
    "label": "Confirm",
    "progress": "todo"
  },
  {
    "label": "Paid",
    "progress": "todo"
  }
]} title="Expired" message={{
  "$slot": [
    "Nothing arrived in time; ask for a new order."
  ]
}} state="expired" />

HTML

<div class="mk-stage state-expired"><ol class="mk-track" aria-label="Payment progress"><li class="step-fail"><span class="mk-track-dot" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><path d="M7 7l10 10M17 7 7 17"></path></svg></span><span class="mk-track-name">Send<span class="visually-hidden">, stopped</span></span></li><li class="step-todo"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Confirm<span class="visually-hidden">, to come</span></span></li><li class="step-todo"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Paid<span class="visually-hidden">, to come</span></span></li></ol><p class="mk-stage-msg" role="status"><strong>Expired.</strong> Nothing arrived in time; ask for a new order.</p></div>
On the POS
  1. Send, now
  2. Confirm, to come
  3. Paid, to come

Send 0.42 XMR. Scan the code.

Rust, Solid and HTML

Rust

ProgressSteps::new("Payment progress", vec![
    ProgressStep { label: "Send".into(), progress: StepProgress::Now },
    ProgressStep { label: "Confirm".into(), progress: StepProgress::Todo },
    ProgressStep { label: "Paid".into(), progress: StepProgress::Todo },
], "Send 0.42 XMR", "Scan the code.")
    .state(OrderState::Pending)
    .compact(true)

Solid

<ProgressSteps label="Payment progress" steps={[
  {
    "label": "Send",
    "progress": "now"
  },
  {
    "label": "Confirm",
    "progress": "todo"
  },
  {
    "label": "Paid",
    "progress": "todo"
  }
]} title="Send 0.42 XMR" message={{
  "$slot": [
    "Scan the code."
  ]
}} state="pending" compact />

HTML

<div class="mk-stage state-pending" data-size="compact"><ol class="mk-track" aria-label="Payment progress"><li class="step-now"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Send<span class="visually-hidden">, now</span></span></li><li class="step-todo"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Confirm<span class="visually-hidden">, to come</span></span></li><li class="step-todo"><span class="mk-track-dot" aria-hidden="true"></span><span class="mk-track-name">Paid<span class="visually-hidden">, to come</span></span></li></ol><p class="mk-stage-msg" role="status"><strong>Send 0.42 XMR.</strong> Scan the code.</p></div>

Tabs

Panels of one page, one shown at a time. Without JavaScript every panel shows, and the tabs are links to them. Once its island attaches, the tabs are a tablist: one panel at a time, picked with a click or the arrow keys, Home and End. Each pick sends mk-tab ({id}, the panel shown) from the tabs, for a page script to follow.

Props
PropTypeAbout
labelStringRequired. What the tabs are of.
tabsVec<Tab>Required. One tab of [Tabs]: its label and the panel it shows.
restAttrs
A POS's views
Enter an amount to start a payment.
No payments yet.
This terminal is ready to receive payments.
Rust, Solid and HTML

Rust

Tabs::new("Example POS views", vec![
    Tab { id: "example-charge".into(), label: "Charge".into(), panel: "Enter an amount to start a payment.".into() },
    Tab { id: "example-history".into(), label: "History".into(), panel: "No payments yet.".into() },
    Tab { id: "example-terminal".into(), label: "Terminal".into(), panel: "This terminal is ready to receive payments.".into() },
])

Solid

<Tabs label="Example POS views" tabs={[
  {
    "id": "example-charge",
    "label": {
      "$slot": [
        "Charge"
      ]
    },
    "panel": {
      "$slot": [
        "Enter an amount to start a payment."
      ]
    }
  },
  {
    "id": "example-history",
    "label": {
      "$slot": [
        "History"
      ]
    },
    "panel": {
      "$slot": [
        "No payments yet."
      ]
    }
  },
  {
    "id": "example-terminal",
    "label": {
      "$slot": [
        "Terminal"
      ]
    },
    "panel": {
      "$slot": [
        "This terminal is ready to receive payments."
      ]
    }
  }
]} />

HTML

<div class="mk-tabset" data-mk="1" data-island="Tabs" data-props="{&quot;tabs&quot;:[{&quot;id&quot;:&quot;example-charge&quot;},{&quot;id&quot;:&quot;example-history&quot;},{&quot;id&quot;:&quot;example-terminal&quot;}]}"><div class="mk-tabs" aria-label="Example POS views" data-mk="2"><a href="#example-charge" id="example-charge-tab" data-mk="3">Charge</a><a href="#example-history" id="example-history-tab" data-mk="3">History</a><a href="#example-terminal" id="example-terminal-tab" data-mk="3">Terminal</a></div><div class="mk-tab-panels"><section id="example-charge" data-tab-panel="" data-mk="4">Enter an amount to start a payment.</section><section id="example-history" data-tab-panel="" data-mk="4">No payments yet.</section><section id="example-terminal" data-tab-panel="" data-mk="4">This terminal is ready to receive payments.</section></div></div>

CopyValue

A long value to copy: an address, an id, a key. Without JavaScript it is selectable text, ellipsised, that focus shows in full. Once its island attaches, a Copy button copies the whole value (or, where the clipboard is refused, selects it to copy by hand), and a shortened form can stand in for it on screen.

Props
PropTypeAbout
valueStringRequired. The whole value: what is copied.
copy_labelStringThe Copy button's accessible name: what it copies. Default: "Copy full value".
labelled_byOption<String>The id of what names the value, when something on the page does.
truncateTruncate (end | middle | segments)How a long value is shortened on screen. The whole value is always what is copied, and what focus reveals. Default: end.
headnumberMiddle's characters kept at the start. Default: 8.
tailnumberMiddle's characters kept at the end. Default: 8.
segmentsOption<String>Segments' ranges.
Ellipsised at the end
order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789
Rust, Solid and HTML

Rust

CopyValue::new("order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789")
    .copy_label("Copy payment reference")

Solid

<CopyValue value="order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789" copy_label="Copy payment reference" />

HTML

<div class="mk-copy" data-island="CopyValue" data-props="{&quot;copy_label&quot;:&quot;Copy payment reference&quot;,&quot;head&quot;:8,&quot;tail&quot;:8,&quot;truncate&quot;:&quot;end&quot;,&quot;value&quot;:&quot;order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789&quot;}"><span class="mk-copy-value mk-mono" data-copy-value tabindex="0" data-mk="1">order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789</span><!--mk:2--></div>
Middle: 10 characters at each end
order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789
Rust, Solid and HTML

Rust

CopyValue::new("order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789")
    .copy_label("Copy middle example")
    .truncate(Truncate::Middle)
    .head(10)
    .tail(10)

Solid

<CopyValue value="order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789" copy_label="Copy middle example" truncate="middle" head={10} tail={10} />

HTML

<div class="mk-copy" data-island="CopyValue" data-props="{&quot;copy_label&quot;:&quot;Copy middle example&quot;,&quot;head&quot;:10,&quot;tail&quot;:10,&quot;truncate&quot;:&quot;middle&quot;,&quot;value&quot;:&quot;order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789&quot;}"><span class="mk-copy-value mk-mono" data-copy-value tabindex="0" data-mk="1">order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789</span><!--mk:2--></div>
Segments: prefix, internal id, suffix
order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789
Rust, Solid and HTML

Rust

CopyValue::new("order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789")
    .copy_label("Copy segmented example")
    .truncate(Truncate::Segments)
    .segments("0:8,32:8,-8:8")

Solid

<CopyValue value="order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789" copy_label="Copy segmented example" truncate="segments" segments="0:8,32:8,-8:8" />

HTML

<div class="mk-copy" data-island="CopyValue" data-props="{&quot;copy_label&quot;:&quot;Copy segmented example&quot;,&quot;head&quot;:8,&quot;segments&quot;:&quot;0:8,32:8,-8:8&quot;,&quot;tail&quot;:8,&quot;truncate&quot;:&quot;segments&quot;,&quot;value&quot;:&quot;order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789&quot;}"><span class="mk-copy-value mk-mono" data-copy-value tabindex="0" data-mk="1">order_0123456789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789</span><!--mk:2--></div>
Short enough to show whole
48xQ7v3Rk
Rust, Solid and HTML

Rust

CopyValue::new("48xQ7v3Rk")
    .copy_label("Copy wallet id")

Solid

<CopyValue value="48xQ7v3Rk" copy_label="Copy wallet id" />

HTML

<div class="mk-copy" data-island="CopyValue" data-props="{&quot;copy_label&quot;:&quot;Copy wallet id&quot;,&quot;head&quot;:8,&quot;tail&quot;:8,&quot;truncate&quot;:&quot;end&quot;,&quot;value&quot;:&quot;48xQ7v3Rk&quot;}"><span class="mk-copy-value mk-mono" data-copy-value tabindex="0" data-mk="1">48xQ7v3Rk</span><!--mk:2--></div>

CopyBlock

Lines to copy whole: a command, a config file, a key. Without JavaScript it is a pre to select. Once its island attaches, a Copy button over its top right copies it; with comment ("#"), a note after that marker is left out (the marker starting a line or after a space: # inside a word stays).

Props
PropTypeAbout
codeStringRequired.
copy_labelStringThe Copy button's accessible name: what it copies. Default: "Copy to clipboard".
commentOption<String>A line comment's marker: what follows it isn't copied.
A command with a note
umask 077
openssl rand -hex 32 > monokulo.key   # keep this file safe
Rust, Solid and HTML

Rust

CopyBlock::new("umask 077\nopenssl rand -hex 32 > monokulo.key   # keep this file safe")
    .copy_label("Copy the key commands")
    .comment("#")

Solid

<CopyBlock code="umask 077\nopenssl rand -hex 32 > monokulo.key   # keep this file safe" copy_label="Copy the key commands" comment="#" />

HTML

<div class="mk-copy-block" data-island="CopyBlock" data-props="{&quot;comment&quot;:&quot;#&quot;,&quot;copy_label&quot;:&quot;Copy the key commands&quot;}"><pre data-mk="1"><code>umask 077
openssl rand -hex 32 &gt; monokulo.key   # keep this file safe</code></pre><!--mk:2--></div>

SettingsForm

The page's settings form: one per page. label names what has unsaved changes when leaving is asked about ("Abuse protection has unsaved changes"). With fixi_target, a save swaps its answer into that element without reloading; without, it's a plain POST and the page it redirects to.

Props
PropTypeAbout
actionStringRequired.
labelStringRequired.
fixi_targetOption<String>
childrenSlotRequired. The cards, the save bar and anything else the form sends.
methodFormMethod (post | get)How a form is sent. Default: post.
form_idStringThe form's id: a page has one settings form, settings-form. Default: "settings-form".
A card, its settings and the save bar

Store details

changed

Saving changes the store straight away.

Rust, Solid and HTML

Rust

SettingsForm::new("#component-settings-form", "Store settings", Slot::many([SettingsCard::new("book-details", "Store details", Setting::new("Store name", "book-settings-name-input", Input::new("name")
    .id("book-settings-name-input")
    .value("Corner store"))), SaveBar::new("Saving changes the store straight away.", "#component-settings-form")
    .inline(true)
    .id("book-form-save-bar")
    .region_label("Example form's save changes")]))
    .method(FormMethod::Get)
    .form_id("book-settings-form")

Solid

<SettingsForm action="#component-settings-form" label="Store settings" method="get" form_id="book-settings-form"><SettingsCard name="book-details" title="Store details"><Setting label="Store name" id="book-settings-name-input"><Input name="name" id="book-settings-name-input" value="Corner store" /></Setting></SettingsCard><SaveBar message={{
  "$slot": [
    "Saving changes the store straight away."
  ]
}} discard_href="#component-settings-form" inline id="book-form-save-bar" region_label="Example form's save changes" /></SettingsForm>

HTML

<div class="mk-settings-form" data-label="Store settings" data-mk="1" data-island="SettingsForm" data-props="{&quot;label&quot;:&quot;Store settings&quot;}"><form method="get" action="#component-settings-form" id="book-settings-form" data-mk="2"><section id="card-book-details" class="settings-card" data-name="book-details" role="region" aria-labelledby="card-book-details-title" data-mk="1" data-island="SettingsCard" data-props="{&quot;failed&quot;:false,&quot;hidden&quot;:false,&quot;name&quot;:&quot;book-details&quot;,&quot;title&quot;:&quot;Store details&quot;}"><header class="card-head"><h3 id="card-book-details-title">Store details</h3><span class="card-state" data-card-state><!--mk:2--></span><span class="card-spacer"></span><!--mk:5--></header><div class="card-body"><div class="setting-field" data-mk="1" data-island="Setting" data-props="{}"><div class="setting-about"><div class="setting-label-row"><label class="setting-label" for="book-settings-name-input">Store name</label><span class="changed-mark">changed</span></div></div><div class="setting-control"><input class="mk-input" type="text" name="name" id="book-settings-name-input" value="Corner store"></div></div></div></section><div id="book-form-save-bar" class="save-bar" data-placement="inline" role="region" aria-label="Example form's save changes" data-mk="1" data-island="SaveBar" data-props="{&quot;failed&quot;:false,&quot;save_label&quot;:&quot;Save&quot;}"><div class="save-bar-inner"><p class="save-bar-message" data-save-bar-message tabindex="-1"><span data-mk="2">Saving changes the store straight away.</span><!--mk:3--></p><div class="save-bar-actions" data-mk="4"><a class="mk-btn" href="#component-settings-form" data-discard-all data-mk="5">Discard changes</a><button type="submit" class="mk-btn" data-variant="primary" data-save data-mk="6"><!--mk:7-->Save<!--/mk:7--></button></div><!--mk:8--></div></div></form></div>

SettingsCard

One card of settings, saved together: its title, what its unsaved changes or a refusal say, "Saved" after a save, and Discard for its changes. Its id is card-{name}: links to it (the save bar's, a toast's Show) use it.

Props
PropTypeAbout
nameStringRequired. The card's name: card-{name} is its id, and events name it.
titleStringRequired.
childrenSlotRequired. Its settings.
extra_classOption<String>Another class beside settings-card.
headOption<Slot>After the title: a chip, a line of meta.
linkOption<Slot>At the right of the head: where more about it is shown.
badgeOption<Slot>In the head while the card isn't refused ("Restart needed").
failedboolThe last save refused this card: "Not saved", and message. Default: false.
messageOption<String>
savedOption<String>The last save saved this card: "Saved" and when (or nothing).
focus_savedboolThe "Saved" mark takes focus once fixi swaps it in. Default: false.
readonlyboolNothing on it can be changed here. Default: false.
hiddenboolHidden now: shown_by shows it. Default: false.
shown_byOption<String>name=value: shown only while that checkbox is ticked, its controls off (not sent) while hidden.
restAttrsdata-* attributes of the page's own.
Saved

Payments

Saved 12 Oct, 09:30
Its settings.
Rust, Solid and HTML

Rust

SettingsCard::new("book-saved", "Payments", "Its settings.")
    .saved("12 Oct, 09:30")

Solid

<SettingsCard name="book-saved" title="Payments" saved="12 Oct, 09:30">Its settings.</SettingsCard>

HTML

<section id="card-book-saved" class="settings-card" data-name="book-saved" role="region" aria-labelledby="card-book-saved-title" data-mk="1" data-island="SettingsCard" data-props="{&quot;failed&quot;:false,&quot;hidden&quot;:false,&quot;name&quot;:&quot;book-saved&quot;,&quot;title&quot;:&quot;Payments&quot;}"><header class="card-head"><h3 id="card-book-saved-title">Payments</h3><span class="card-state" data-card-state><!--mk:2--></span><span class="card-spacer"></span><span class="card-meta card-saved" data-card-saved data-mk="4">Saved 12 Oct, 09:30</span><!--mk:5--></header><div class="card-body">Its settings.</div></section>
Refused

Webhooks

Not saved
Its settings.
Rust, Solid and HTML

Rust

SettingsCard::new("book-refused", "Webhooks", "Its settings.")
    .failed(true)
    .message("The URL must use https.")

Solid

<SettingsCard name="book-refused" title="Webhooks" failed message="The URL must use https.">Its settings.</SettingsCard>

HTML

<section id="card-book-refused" class="settings-card is-failed" data-name="book-refused" role="region" aria-labelledby="card-book-refused-title" data-mk="1" data-island="SettingsCard" data-props="{&quot;failed&quot;:true,&quot;hidden&quot;:false,&quot;name&quot;:&quot;book-refused&quot;,&quot;title&quot;:&quot;Webhooks&quot;}"><header class="card-head"><h3 id="card-book-refused-title">Webhooks</h3><span class="card-state" data-card-state><!--mk:2--><span class="mk-status-text" data-tone="error" data-card-failed data-mk="3">Not saved</span></span><span class="card-spacer"></span><!--mk:5--></header><div class="card-body"><p class="error" role="alert" data-mk="6">The URL must use https.</p>Its settings.</div></section>
Read only

Server

Can't be changed here
Its settings.
Rust, Solid and HTML

Rust

SettingsCard::new("book-readonly", "Server", "Its settings.")
    .readonly(true)

Solid

<SettingsCard name="book-readonly" title="Server" readonly>Its settings.</SettingsCard>

HTML

<section id="card-book-readonly" class="settings-card" data-name="book-readonly" role="region" aria-labelledby="card-book-readonly-title" data-mk="1" data-island="SettingsCard" data-props="{&quot;failed&quot;:false,&quot;hidden&quot;:false,&quot;name&quot;:&quot;book-readonly&quot;,&quot;title&quot;:&quot;Server&quot;}"><header class="card-head"><h3 id="card-book-readonly-title">Server</h3><span class="card-state" data-card-state><!--mk:2--></span><span class="card-spacer"></span><span class="card-meta">Can't be changed here</span></header><div class="card-body">Its settings.</div></section>

Setting

One setting: its name and what it's for on the left, its control on the right (a phone, or a wide control such as radio cards, a list of choices, a text area or a table, puts the control under them), and "changed" while it differs from what's saved (a control's data-saved after a refused save, its default value otherwise).

Props
PropTypeAbout
labelStringRequired.
idStringRequired. The id of the control the label is for; for a group, the label's own id, which names the group.
childrenSlotRequired. The control, and anything under it.
groupboolSeveral controls (radio buttons, checkboxes): a group named by its label. Default: false.
chipOption<Slot>Beside the label: where its value comes from.
helpOption<Slot>What it's for, under the label.
help_idOption<String>The help's id, for the control to point at.
described_byOption<String>A group's description: its help's id.
label_idOption<String>The label's own id, for a control that names itself by it.
restAttrsdata-* attributes of the page's own, hidden.
With help
changed

Blocks a payment waits for before it counts as paid.

Rust, Solid and HTML

Rust

Setting::new("Confirmations", "book-setting-confirmations", Input::new("confirmations")
    .id("book-setting-confirmations")
    .kind(InputType::Number)
    .value("10")
    .described_by("book-setting-confirmations-help"))
    .help("Blocks a payment waits for before it counts as paid.")
    .help_id("book-setting-confirmations-help")

Solid

<Setting label="Confirmations" id="book-setting-confirmations" help={{
  "$slot": [
    "Blocks a payment waits for before it counts as paid."
  ]
}} help_id="book-setting-confirmations-help"><Input name="confirmations" id="book-setting-confirmations" kind="number" value="10" described_by="book-setting-confirmations-help" /></Setting>

HTML

<div class="setting-field" data-mk="1" data-island="Setting" data-props="{}"><div class="setting-about"><div class="setting-label-row"><label class="setting-label" for="book-setting-confirmations">Confirmations</label><span class="changed-mark">changed</span></div><p class="field-help" id="book-setting-confirmations-help">Blocks a payment waits for before it counts as paid.</p></div><div class="setting-control"><input class="mk-input" type="number" name="confirmations" id="book-setting-confirmations" value="10" aria-describedby="book-setting-confirmations-help"></div></div>
A checkbox
changed

Connect with https. Most nodes on port 18081 don't use it.

Rust, Solid and HTML

Rust

Setting::new("Use TLS", "book-setting-tls", Checkbox::new()
    .name("tls")
    .id("book-setting-tls")
    .described_by("book-setting-tls-help"))
    .help("Connect with https. Most nodes on port 18081 don't use it.")
    .help_id("book-setting-tls-help")

Solid

<Setting label="Use TLS" id="book-setting-tls" help={{
  "$slot": [
    "Connect with https. Most nodes on port 18081 don't use it."
  ]
}} help_id="book-setting-tls-help"><Checkbox name="tls" id="book-setting-tls" described_by="book-setting-tls-help" /></Setting>

HTML

<div class="setting-field" data-mk="1" data-island="Setting" data-props="{}"><div class="setting-about"><div class="setting-label-row"><label class="setting-label" for="book-setting-tls">Use TLS</label><span class="changed-mark">changed</span></div><p class="field-help" id="book-setting-tls-help">Connect with https. Most nodes on port 18081 don't use it.</p></div><div class="setting-control"><input type="checkbox" class="mk-checkbox" name="tls" id="book-setting-tls" value="on" aria-describedby="book-setting-tls-help"></div></div>
A group
Acceptchanged

What a payment may arrive as.

Its checkboxes.
Rust, Solid and HTML

Rust

Setting::new("Accept", "book-setting-accept", "Its checkboxes.")
    .group(true)
    .help("What a payment may arrive as.")
    .help_id("book-setting-accept-help")
    .described_by("book-setting-accept-help")

Solid

<Setting label="Accept" id="book-setting-accept" group help={{
  "$slot": [
    "What a payment may arrive as."
  ]
}} help_id="book-setting-accept-help" described_by="book-setting-accept-help">Its checkboxes.</Setting>

HTML

<div class="setting-field" role="group" aria-labelledby="book-setting-accept" aria-describedby="book-setting-accept-help" data-mk="1" data-island="Setting" data-props="{}"><div class="setting-about"><div class="setting-label-row"><span class="setting-label" id="book-setting-accept">Accept</span><span class="changed-mark">changed</span></div><p class="field-help" id="book-setting-accept-help">What a payment may arrive as.</p></div><div class="setting-control">Its checkboxes.</div></div>

SaveBar

The page's one save bar, along the bottom of the window: message, then Discard changes (a link to the page as saved) and Save. Without JavaScript it's always there; with it, only while something changed, a save was refused, or leaving is asked about, and it names the cards changed. Red after a save that refused something.

Props
PropTypeAbout
messageSlotRequired. What saving does, or why the last save didn't.
discard_hrefStringRequired. The page as saved.
failedboolDefault: false.
focusboolThe message takes focus once fixi swaps it in. Default: false.
inlineboolIn the page's footer, not along the bottom of the window. Default: false.
save_labelStringWhat the Save button says. Default: "Save".
idStringIts id: a page has one save bar, save-bar. Default: "save-bar".
region_labelStringIts region's name. Default: "Save changes".
Its message

Saving writes the changes on this tab.

Rust, Solid and HTML

Rust

SaveBar::new("Saving writes the changes on this tab.", "#component-save-bar")
    .inline(true)
    .id("book-save-bar")
    .region_label("Example save bar")

Solid

<SaveBar message={{
  "$slot": [
    "Saving writes the changes on this tab."
  ]
}} discard_href="#component-save-bar" inline id="book-save-bar" region_label="Example save bar" />

HTML

<div id="book-save-bar" class="save-bar" data-placement="inline" role="region" aria-label="Example save bar" data-mk="1" data-island="SaveBar" data-props="{&quot;failed&quot;:false,&quot;save_label&quot;:&quot;Save&quot;}"><div class="save-bar-inner"><p class="save-bar-message" data-save-bar-message tabindex="-1"><span data-mk="2">Saving writes the changes on this tab.</span><!--mk:3--></p><div class="save-bar-actions" data-mk="4"><a class="mk-btn" href="#component-save-bar" data-discard-all data-mk="5">Discard changes</a><button type="submit" class="mk-btn" data-variant="primary" data-save data-mk="6"><!--mk:7-->Save<!--/mk:7--></button></div><!--mk:8--></div></div>
After a refused save

Nothing saved. The URL must use https.

Rust, Solid and HTML

Rust

SaveBar::new("Nothing saved. The URL must use https.", "#component-save-bar")
    .failed(true)
    .inline(true)
    .id("book-save-bar-refused")
    .region_label("Example refused save bar")

Solid

<SaveBar message={{
  "$slot": [
    "Nothing saved. The URL must use https."
  ]
}} discard_href="#component-save-bar" failed inline id="book-save-bar-refused" region_label="Example refused save bar" />

HTML

<div id="book-save-bar-refused" class="save-bar is-failed" data-placement="inline" role="region" aria-label="Example refused save bar" data-mk="1" data-island="SaveBar" data-props="{&quot;failed&quot;:true,&quot;save_label&quot;:&quot;Save&quot;}"><div class="save-bar-inner"><p class="save-bar-message" data-save-bar-message tabindex="-1"><span data-mk="2">Nothing saved. The URL must use https.</span><!--mk:3--></p><div class="save-bar-actions" data-mk="4"><a class="mk-btn" href="#component-save-bar" data-discard-all data-mk="5">Discard changes</a><button type="submit" class="mk-btn" data-variant="primary" data-save data-mk="6"><!--mk:7-->Save<!--/mk:7--></button></div><!--mk:8--></div></div>

Toast

A small message after a save, in the corner of the window. Without JavaScript it fades by itself; with it, one saying something wasn't saved stays until it's closed.

Props
PropTypeAbout
kindToastKind (success | warning | error | neutral)Required. How a save went.
titleStringRequired.
linesVec<String>Required.
showOption<String>The card to go to (its name): the one a refusal is about.
persistentboolStays until it's closed, whatever it says (a review copy). Default: false.
Saved
Changes saved and applied
Rust, Solid and HTML

Rust

Toast::new(ToastKind::Success, "Changes saved and applied", vec![
])
    .persistent(true)

Solid

<Toast kind="success" title="Changes saved and applied" lines={[]} persistent />

HTML

<div class="toast toast-success" role="status" data-toast data-persistent data-mk="1" data-island="Toast" data-props="{&quot;kind&quot;:&quot;success&quot;}"><span class="toast-icon" aria-hidden="true"><svg class="mk-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4 12 5 5L20 6"></path></svg></span><div class="toast-text"><strong>Changes saved and applied</strong></div><!--mk:2--></div>
Waits for a restart
SavedRestart the engine to apply the new node.
Rust, Solid and HTML

Rust

Toast::new(ToastKind::Warning, "Saved", vec![
    "Restart the engine to apply the new node.",
])
    .persistent(true)

Solid

<Toast kind="warning" title="Saved" lines={[
  "Restart the engine to apply the new node."
]} persistent />

HTML

<div class="toast toast-warning" role="status" data-toast data-persistent data-mk="1" data-island="Toast" data-props="{&quot;kind&quot;:&quot;warning&quot;}"><span class="toast-icon" aria-hidden="true"><svg class="mk-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 3 10 18H2Z M12 9v5M12 17v1"></path></svg></span><div class="toast-text"><strong>Saved</strong><span class="toast-line">Restart the engine to apply the new node.</span></div><!--mk:2--></div>
Refused
Rust, Solid and HTML

Rust

Toast::new(ToastKind::Error, "Nothing saved", vec![
    "The URL must use https.",
])
    .show("book-refused")
    .persistent(true)

Solid

<Toast kind="error" title="Nothing saved" lines={[
  "The URL must use https."
]} show="book-refused" persistent />

HTML

<div class="toast toast-error" role="alert" data-toast data-persistent data-mk="1" data-island="Toast" data-props="{&quot;kind&quot;:&quot;error&quot;}"><span class="toast-icon" aria-hidden="true"><svg class="mk-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 3 10 18H2Z M12 9v5M12 17v1"></path></svg></span><div class="toast-text"><strong>Nothing saved</strong><span class="toast-line">The URL must use https.</span><a class="toast-show" href="#card-book-refused" data-show-card="book-refused">Show</a></div><!--mk:2--></div>
Nothing to save
Nothing changed
Rust, Solid and HTML

Rust

Toast::new(ToastKind::Neutral, "Nothing changed", vec![
])
    .persistent(true)

Solid

<Toast kind="neutral" title="Nothing changed" lines={[]} persistent />

HTML

<div class="toast toast-neutral" role="status" data-toast data-persistent data-mk="1" data-island="Toast" data-props="{&quot;kind&quot;:&quot;neutral&quot;}"><span class="toast-icon" aria-hidden="true"><svg class="mk-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M12 11v6M12 7v1"></path></svg></span><div class="toast-text"><strong>Nothing changed</strong></div><!--mk:2--></div>

SiteNav

The site's bar: the brand on the left, then the links, the status dot and the account menu. On a phone the links fold into a menu behind a hamburger, a checkbox and its label: no JavaScript.

Props
PropTypeAbout
brandSlotRequired. The brand: the logo and the name.
linksSlotRequired. The links, and the account menu once signed in.
homeStringWhere the brand goes. Default: "/".
statusOption<Slot>Beside the hamburger, in sight on a phone: the status dot.
widthPageWidth (normal | wide)The width of the page under it, which its row lines up with. Default: normal.
toggle_idStringThe id of the hamburger's checkbox, unique on the page: another bar on it (the stylebook's specimen) needs its own. Default: "nav-toggle".
Signed out
Rust, Solid and HTML

Rust

SiteNav::new("Monokulo", Slot::many([LinkButton::new("Log in", "#component-site-nav")
    .variant(ButtonVariant::Quiet), LinkButton::new("Sign up", "#component-site-nav")
    .variant(ButtonVariant::Quiet)]))
    .home("#component-site-nav")
    .status(StatusDot::new(HealthState::Ok)
        .href("#component-status-dot")
        .poll("")
        .id("book-nav-status"))
    .toggle_id("book-nav-toggle")

Solid

<SiteNav brand={{
  "$slot": [
    "Monokulo"
  ]
}} links={{
  "$slot": [
    {
      "$component": "LinkButton",
      "props": {
        "block": false,
        "children": {
          "$slot": [
            "Log in"
          ]
        },
        "href": "#component-site-nav",
        "variant": "quiet"
      }
    },
    {
      "$component": "LinkButton",
      "props": {
        "block": false,
        "children": {
          "$slot": [
            "Sign up"
          ]
        },
        "href": "#component-site-nav",
        "variant": "quiet"
      }
    }
  ]
}} home="#component-site-nav" status={{
  "$slot": [
    {
      "$component": "StatusDot",
      "props": {
        "health": "ok",
        "href": "#component-status-dot",
        "id": "book-nav-status",
        "poll": ""
      }
    }
  ]
}} toggle_id="book-nav-toggle" />

HTML

<nav class="site-nav"><div class="wrap site-nav-row"><a href="#component-site-nav" class="site-nav-brand">Monokulo</a><a href="#component-status-dot" class="nav-status-link" id="book-nav-status" title="all systems healthy" aria-label="Status: all systems healthy" data-mk="1" data-island="StatusDot" data-props="{&quot;health&quot;:&quot;ok&quot;,&quot;poll&quot;:&quot;&quot;}"><span class="status-dot status-dot-ok" data-mk="2"></span></a><input type="checkbox" id="book-nav-toggle" class="nav-toggle-checkbox"><label for="book-nav-toggle" class="nav-toggle-label" aria-label="Menu">☰</label><div class="site-nav-links"><a class="mk-btn" href="#component-site-nav" data-variant="quiet">Log in</a><a class="mk-btn" href="#component-site-nav" data-variant="quiet">Sign up</a></div></div></nav>

PageHeader

The top of every page under the site's bar, always in this order: where the page sits (links to the pages above it, the nearest last, "Home › Logs"; one link alone is a back link, with an arrow), its title, then how far through a flow it is. The title is required, so no page goes without one. Beside the title: a status or a badge (aside), and at its right the page's own actions.

Props
PropTypeAbout
titleSlotRequired. The page's h1.
crumbsVec<Crumb>One link of a [PageHeader]'s breadcrumbs. Default: .
asideOption<Slot>Beside the title: a badge, a status, a help tip.
actionsOption<Slot>At the title's right: Reload, a network picker.
progressOption<Slot>Under the title: a flow's progress (WizardSteps).
title_idOption<String>
A title alone

Home

Rust, Solid and HTML

Rust

PageHeader::new("Home")

Solid

<PageHeader title={{
  "$slot": [
    "Home"
  ]
}} />

HTML

<header class="mk-page-header"><div class="mk-page-title"><h1>Home</h1></div></header>
Back to a page

Admin settings

Rust, Solid and HTML

Rust

PageHeader::new("Admin settings")
    .crumbs(vec![
        Crumb { href: "#component-page-header".into(), label: "Home".into() },
    ])

Solid

<PageHeader title={{
  "$slot": [
    "Admin settings"
  ]
}} crumbs={[
  {
    "href": "#component-page-header",
    "label": "Home"
  }
]} />

HTML

<header class="mk-page-header"><nav class="mk-breadcrumbs" aria-label="Breadcrumb"><a href="#component-page-header" title="Home">Home</a></nav><div class="mk-page-title"><h1>Admin settings</h1></div></header>
Two levels up, with an action
Rust, Solid and HTML

Rust

PageHeader::new("Order 4f2a")
    .crumbs(vec![
        Crumb { href: "#component-page-header".into(), label: "Corner store with a long name to see it shortened".into() },
        Crumb { href: "#component-page-header".into(), label: "Orders".into() },
    ])
    .aside(Tag::new("Paid")
        .tone(TagTone::Ok))
    .actions(LinkButton::new("Reload", "#component-page-header")
        .size(ControlSize::Compact))

Solid

<PageHeader title={{
  "$slot": [
    "Order 4f2a"
  ]
}} crumbs={[
  {
    "href": "#component-page-header",
    "label": "Corner store with a long name to see it shortened"
  },
  {
    "href": "#component-page-header",
    "label": "Orders"
  }
]} aside={{
  "$slot": [
    {
      "$component": "Tag",
      "props": {
        "children": {
          "$slot": [
            "Paid"
          ]
        },
        "tone": "ok"
      }
    }
  ]
}} actions={{
  "$slot": [
    {
      "$component": "LinkButton",
      "props": {
        "block": false,
        "children": {
          "$slot": [
            "Reload"
          ]
        },
        "href": "#component-page-header",
        "size": "compact"
      }
    }
  ]
}} />

HTML

<header class="mk-page-header"><nav class="mk-breadcrumbs" aria-label="Breadcrumb"><a href="#component-page-header" title="Corner store with a long name to see it shortened">Corner store with a long name to see it shortened</a><a href="#component-page-header" title="Orders">Orders</a></nav><div class="mk-page-title"><h1>Order 4f2a</h1><span class="tag tag-ok">Paid</span><div class="mk-page-actions"><a class="mk-btn" href="#component-page-header" data-size="compact">Reload</a></div></div></header>
In a flow

Set up a store

  1. StoreIn progress
  2. WalletNext
  3. DoneNext
Rust, Solid and HTML

Rust

PageHeader::new("Set up a store")
    .progress(WizardSteps::new("Setup progress", vec![
        WizardStep { label: "Store".into(), detail: "In progress".into(), done: 0, tasks: 1, state: WizardState::Current },
        WizardStep { label: "Wallet".into(), detail: "Next".into(), done: 0, tasks: 1, state: WizardState::Next },
        WizardStep { label: "Done".into(), detail: "Next".into(), done: 0, tasks: 1, state: WizardState::Next },
    ]))

Solid

<PageHeader title={{
  "$slot": [
    "Set up a store"
  ]
}} progress={{
  "$slot": [
    {
      "$component": "WizardSteps",
      "props": {
        "label": "Setup progress",
        "steps": [
          {
            "detail": "In progress",
            "done": 0,
            "label": "Store",
            "state": "current",
            "tasks": 1
          },
          {
            "detail": "Next",
            "done": 0,
            "label": "Wallet",
            "state": "next",
            "tasks": 1
          },
          {
            "detail": "Next",
            "done": 0,
            "label": "Done",
            "state": "next",
            "tasks": 1
          }
        ]
      }
    }
  ]
}} />

HTML

<header class="mk-page-header"><div class="mk-page-title"><h1>Set up a store</h1></div><ol class="mk-wizard-steps" data-design="strip" aria-label="Setup progress"><li aria-current="step"><span>Store<small>In progress</small></span><progress class="mk-wizard-progress" value="0" max="1" aria-label="Store: 0 of 1 tasks complete"></progress></li><li><span>Wallet<small>Next</small></span><progress class="mk-wizard-progress" value="0" max="1" aria-label="Wallet: 0 of 1 tasks complete"></progress></li><li><span>Done<small>Next</small></span><progress class="mk-wizard-progress" value="0" max="1" aria-label="Done: 0 of 1 tasks complete"></progress></li></ol></header>

Menu

A menu behind a button: a details, so it opens and closes without JavaScript. Once its island attaches, a click outside it or Escape closes it too, Escape putting focus back on its button.

Props
PropTypeAbout
labelStringRequired. The button's accessible name ("Account menu").
buttonSlotRequired. What the button shows (an icon).
childrenSlotRequired. The menu's links and controls.
titleOption<String>The button's tooltip (the signed-in email).
The account menu
Rust, Solid and HTML

Rust

Menu::new("Example account menu", "A", Slot::many([MenuItem::new("Account")
    .href("#component-menu"), MenuItem::new("Wallets")
    .href("#component-menu")
    .note("2")]))
    .title("alex@example.test")

Solid

<Menu label="Example account menu" button={{
  "$slot": [
    "A"
  ]
}} title="alex@example.test"><MenuItem href="#component-menu">Account</MenuItem><MenuItem href="#component-menu" note={{
  "$slot": [
    "2"
  ]
}}>Wallets</MenuItem></Menu>

HTML

<details class="acct" data-mk="1" data-island="Menu" data-props="{}"><summary class="acct-button" aria-label="Example account menu" title="alex@example.test">A</summary><div class="acct-menu"><a class="menu-item" href="#component-menu"><span>Account</span></a><a class="menu-item" href="#component-menu"><span>Wallets</span><span class="menu-note">2</span></a></div></details>

MenuItem

One row of a Menu: a link, a form's button (Log out, a POST), or a row holding a control (the theme switch). A note sits at its right: a count, "change".

Props
PropTypeAbout
childrenSlotRequired.
hrefOption<String>
post_toOption<String>Posts here when chosen.
noteOption<Slot>
titleOption<String>
restAttrsOn the row's element (the link, the form or the row).
A link with a note
Rust, Solid and HTML

Rust

MenuItem::new("Wallets")
    .href("#component-menu-item")
    .note("1")

Solid

<MenuItem href="#component-menu-item" note={{
  "$slot": [
    "1"
  ]
}}>Wallets</MenuItem>

HTML

<a class="menu-item" href="#component-menu-item"><span>Wallets</span><span class="menu-note">1</span></a>
A form's button
Rust, Solid and HTML

Rust

MenuItem::new("Log out")
    .post_to("#component-menu-item")

Solid

<MenuItem post_to="#component-menu-item">Log out</MenuItem>

HTML

<form method="post" action="#component-menu-item" class="menu-form"><button type="submit" class="menu-item"><span>Log out</span></button></form>

ThemeToggle

The theme switch: System, Light and Dark, each a submit button, so it works without JavaScript; the thumb slides to the one hovered.

Props
PropTypeAbout
themeThemeChoice (system | light | dark)Required. A colour theme.
actionStringRequired. Where the choice is posted.
nextStringRequired. The page to come back to.
Following the system
Rust, Solid and HTML

Rust

ThemeToggle::new(ThemeChoice::System, "#component-theme-toggle", "/")

Solid

<ThemeToggle theme="system" action="#component-theme-toggle" next="/" />

HTML

<form method="post" action="#component-theme-toggle" class="nav-theme-form"><input type="hidden" name="next" value="/"><div class="theme-toggle theme-toggle-system" role="group" aria-label="Theme"><span class="theme-toggle-track"><button type="submit" name="theme" value="system" class="theme-toggle-option theme-toggle-option-system" aria-label="System theme" aria-pressed="true" title="System theme"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="13" rx="2"></rect><line x1="12" y1="16" x2="12" y2="21"></line><line x1="8" y1="21" x2="16" y2="21"></line></svg></button><button type="submit" name="theme" value="light" class="theme-toggle-option theme-toggle-option-light" aria-label="Light theme" aria-pressed="false" title="Light theme"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"></circle><line x1="12" y1="2" x2="12" y2="4"></line><line x1="12" y1="20" x2="12" y2="22"></line><line x1="4.2" y1="4.2" x2="5.6" y2="5.6"></line><line x1="18.4" y1="18.4" x2="19.8" y2="19.8"></line><line x1="2" y1="12" x2="4" y2="12"></line><line x1="20" y1="12" x2="22" y2="12"></line><line x1="4.2" y1="19.8" x2="5.6" y2="18.4"></line><line x1="18.4" y1="5.6" x2="19.8" y2="4.2"></line></svg></button><button type="submit" name="theme" value="dark" class="theme-toggle-option theme-toggle-option-dark" aria-label="Dark theme" aria-pressed="false" title="Dark theme"><svg viewBox="0 0 24 24" fill="currentColor" stroke="none" aria-hidden="true"><path d="M21 12.5A9 9 0 1 1 11.5 3 7 7 0 0 0 21 12.5Z"></path></svg></button><span class="theme-toggle-thumb"></span></span></div></form>

StatusDot

A glowing dot linking to the status page (in the site's bar and the POS's alike), as the instance was when the page was rendered, so it is right without JavaScript. Once its island attaches it polls poll every 30 seconds while the tab is in sight. Its title and accessible name carry the words.

Props
PropTypeAbout
healthHealthState (ok | slow | error | unknown)Required. How the instance is, as the status dot shows it.
hrefStringDefault: "/status".
pollStringAnswers {"state": "ok" | "slow" | ...}. Default: "/status/summary".
idStringIts id: a page has one, status-indicator. Default: "status-indicator".
Healthy
Rust, Solid and HTML

Rust

StatusDot::new(HealthState::Ok)
    .href("#component-status-dot")
    .poll("")
    .id("book-status-ok")

Solid

<StatusDot health="ok" href="#component-status-dot" poll="" id="book-status-ok" />

HTML

<a href="#component-status-dot" class="nav-status-link" id="book-status-ok" title="all systems healthy" aria-label="Status: all systems healthy" data-mk="1" data-island="StatusDot" data-props="{&quot;health&quot;:&quot;ok&quot;,&quot;poll&quot;:&quot;&quot;}"><span class="status-dot status-dot-ok" data-mk="2"></span></a>
Slow
Rust, Solid and HTML

Rust

StatusDot::new(HealthState::Slow)
    .href("#component-status-dot")
    .poll("")
    .id("book-status-slow")

Solid

<StatusDot health="slow" href="#component-status-dot" poll="" id="book-status-slow" />

HTML

<a href="#component-status-dot" class="nav-status-link" id="book-status-slow" title="scanning is slow - see the status page" aria-label="Status: scanning is slow - see the status page" data-mk="1" data-island="StatusDot" data-props="{&quot;health&quot;:&quot;slow&quot;,&quot;poll&quot;:&quot;&quot;}"><span class="status-dot status-dot-slow" data-mk="2"></span></a>
A problem
Rust, Solid and HTML

Rust

StatusDot::new(HealthState::Error)
    .href("#component-status-dot")
    .poll("")
    .id("book-status-error")

Solid

<StatusDot health="error" href="#component-status-dot" poll="" id="book-status-error" />

HTML

<a href="#component-status-dot" class="nav-status-link" id="book-status-error" title="an issue was detected - see the status page" aria-label="Status: an issue was detected - see the status page" data-mk="1" data-island="StatusDot" data-props="{&quot;health&quot;:&quot;error&quot;,&quot;poll&quot;:&quot;&quot;}"><span class="status-dot status-dot-error" data-mk="2"></span></a>