Design-Language/02-component-recipes.md
Component Recipes — Crawler Dashboard Design Language
Purpose: copy-paste-ready HTML / JSX skeletons for every pattern in
01-design-language.md. Use these before authoring any new component. If a recipe matches your need, use it.Convention: examples are framework-agnostic HTML using class names from
tokens.css. To use in React, replaceclasswithclassNameand convert attributes to camelCase. State props are noted in comments.
Recipe index
- Setting card (read mode)
- Setting card (edit mode, inline)
- Toggle row
- Toggle → conditional fields
- Number stepper with unit
- Repeating row (Start URLs / Exclusion Rules)
- Read-only with copy / Masked secret
- Day-picker pills
- Create-entity modal
- Destructive typed-confirm
- Status pill
- KPI tile row
- Empty state — data / gated / edit-empty
- Page header with tabs
- Three-section left nav
- Persistent CTA top bar
- Toolbar above table
- Data table
- Info banner inline
- Suggestion card
- Grouped warnings (Data Analysis)
- Run-control cluster (workspace top-right)
- Run-state pill (list row)
- Re-crawl confirm dialog (scoped, not typed)
1. Setting card (read mode)
<article class="card">
<div style="display: flex; align-items: flex-start; gap: 16px;">
<div class="card__icon-tile">
<svg width="18" height="18" aria-hidden="true">…</svg>
</div>
<div style="flex: 1;">
<h3 class="t-card-title">Crawler Schedule</h3>
<p class="t-description">Tell the crawler how often you want it to crawl your site.</p>
</div>
<div style="display: flex; align-items: center; gap: 16px;">
<span class="t-description">on the 28 day of the month</span>
<button class="btn btn--ghost btn--small" aria-label="Edit Crawler Schedule">Edit</button>
</div>
</div>
</article>
Notes: - Icon tile = 32px square, light grey fill, 8px radius, dark monochrome glyph. - The right-side "value" is muted text — never bold, never colored. - The Edit button is the only interactive element.
2. Setting card (edit mode, inline)
<article class="card" data-state="editing">
<div style="display: flex; align-items: flex-start; gap: 16px;">
<div class="card__icon-tile">…</div>
<div style="flex: 1;">
<h3 class="t-card-title">Crawler Schedule</h3>
<p class="t-description">Tell the crawler how often you want it to crawl your site.</p>
</div>
</div>
<!-- Edit body -->
<div style="margin-top: 20px; display: flex; flex-direction: column; gap: 20px;">
<!-- ... controls go here (use Recipe 3-8 inside) ... -->
</div>
<!-- Save / Cancel row -->
<div style="display: flex; gap: 12px; margin-top: 24px;">
<button class="btn btn--primary" id="save" disabled>Save</button>
<button class="btn btn--ghost" id="cancel">Cancel</button>
</div>
</article>
Rules: - Save is disabled (greyed) until the form is dirty; becomes solid blue when dirty. - Cancel is always available; reverts state and collapses card back to read mode. - No "delete" button inside the edit body — use the danger zone (Recipe 10) for destructive operations.
3. Toggle row
<div style="display: flex; align-items: center; gap: 12px;">
<label class="switch">
<input type="checkbox" checked>
<span class="switch__track"></span>
</label>
<span class="t-body">Enabled</span>
</div>
Rules: - Always pair the switch with a textual label ("Enabled" / "Disabled") — never rely on track color alone.
4. Toggle → conditional fields
<!-- Top toggle -->
<div style="display: flex; align-items: center; gap: 12px;">
<label class="switch"><input type="checkbox" checked><span class="switch__track"></span></label>
<span class="t-body">Enabled</span>
</div>
<!-- Conditional dropdown -->
<select class="input">
<option>Basic Auth: Username & Password</option>
<option>OAuth 2.0</option>
</select>
<!-- Conditional fieldset, labelled with the selected variant -->
<fieldset style="border: 1px solid var(--line-default); border-radius: 12px; padding: 20px;">
<legend class="t-label-caps" style="padding: 0 8px;">BASIC AUTH: USERNAME & PASSWORD</legend>
<div style="display: flex; flex-direction: column; gap: 16px;">
<div>
<label class="t-description">URL</label>
<div style="display: flex; gap: 8px;">
<select class="input" style="width: 100px;"><option>https://</option></select>
<input class="input" type="text">
</div>
</div>
<div>
<label class="t-description">Username</label>
<input class="input" type="text">
</div>
<div>
<label class="t-description">Password</label>
<input class="input" type="password">
</div>
</div>
</fieldset>
Rules: - Three levels of progressive disclosure max. Don't nest deeper. - Fieldset legend repeats the dropdown selection so user remembers their choice.
5. Number stepper with unit
<div style="display: flex; align-items: center; gap: 8px;">
<span class="t-body">The crawler will stop when it has crawled</span>
<input class="input" type="number" value="100" style="width: 100px;">
<span class="t-description">URLs</span>
</div>
Rule: when a number has a unit, surround the input with natural-language framing ("when it has crawled X URLs"). It's more readable than a labelled input alone.
6. Repeating row
<div style="display: flex; flex-direction: column; gap: 12px;">
<!-- Header row of column labels -->
<div style="display: grid; grid-template-columns: 200px 1fr 40px; gap: 12px;">
<span class="t-description">Condition</span>
<span class="t-description">URL Pattern</span>
<span></span>
</div>
<!-- Row -->
<div style="display: grid; grid-template-columns: 200px 1fr 40px; gap: 12px;">
<select class="input"><option>Select condition</option></select>
<input class="input" type="text" placeholder="Enter pattern…">
<button class="btn btn--ghost btn--small" aria-label="Remove rule">
<svg width="16" height="16">…trash icon…</svg>
</button>
</div>
<!-- Add button (ghost, full-width or left-aligned, with + glyph) -->
<button class="btn btn--ghost btn--small" style="align-self: flex-start;">
+ URL Exclusion
</button>
</div>
Rules:
- Add button is ghost-style with a + glyph, always below the rows.
- Trash is a 40px icon button (icon-only with aria-label).
- Removing the last row is allowed; the empty state appears below the add button.
7. Read-only with copy / Masked secret
<!-- Read-only with copy -->
<label class="t-description">Crawler ID</label>
<div style="display: flex; gap: 8px;">
<input class="input" readonly value="0cd7586b-a2be-4340-bd23-73625b38f52a">
<button class="btn btn--ghost btn--small" aria-label="Copy Crawler ID">
<svg>…copy icon…</svg>
</button>
</div>
<!-- Masked secret with reveal + copy -->
<label class="t-description">Algolia API Key</label>
<div style="display: flex; gap: 8px;">
<input class="input" type="password" readonly value="••••••••••••••••••••••••••">
<button class="btn btn--ghost btn--small" aria-label="Reveal API Key"><svg>…eye…</svg></button>
<button class="btn btn--ghost btn--small" aria-label="Copy API Key"><svg>…copy…</svg></button>
</div>
8. Day-picker pills
<div style="display: flex; gap: 6px;">
<button class="day-pill" aria-pressed="false">Su</button>
<button class="day-pill" aria-pressed="true">M</button>
<button class="day-pill" aria-pressed="false">Tu</button>
<button class="day-pill" aria-pressed="false">W</button>
<button class="day-pill" aria-pressed="false">Th</button>
<button class="day-pill" aria-pressed="false">F</button>
<button class="day-pill" aria-pressed="false">S</button>
</div>
<style>
.day-pill {
width: 36px; height: 36px;
border: 1px solid var(--line-default);
border-radius: 8px;
background: var(--surface-card);
font-size: var(--text-description);
font-weight: var(--weight-semibold);
cursor: pointer;
}
.day-pill[aria-pressed="true"] {
background: var(--accent);
color: var(--ink-on-accent);
border-color: var(--accent);
}
</style>
9. Create-entity modal
<div class="dialog-backdrop" role="presentation">
<div class="dialog" role="dialog" aria-labelledby="dialog-title" aria-modal="true">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h2 class="dialog__title" id="dialog-title">Add Markdown Index</h2>
<button class="btn btn--ghost btn--small" aria-label="Close">×</button>
</div>
<div class="dialog__body">
<div>
<label class="t-description">Index Name <span style="color: var(--status-danger-fg);">*</span></label>
<input class="input" type="text" placeholder="Enter index name (e.g., markdown-index)">
</div>
<div>
<label class="t-description">Content Tag <span style="color: var(--status-danger-fg);">*</span></label>
<input class="input" type="text" value="main">
</div>
<div>
<label class="t-description">Template <span style="color: var(--status-danger-fg);">*</span></label>
<select class="input">
<option>Non-DocSearch (Generic)</option>
<option>DocSearch</option>
</select>
<p class="input-help">Choose the template that matches your documentation framework for optimal indexing</p>
</div>
</div>
<div class="dialog__actions">
<button class="btn btn--ghost">Cancel</button>
<button class="btn btn--primary">Add Index</button>
</div>
</div>
</div>
10. Destructive typed-confirm
<!-- The danger zone card on the Settings page -->
<article class="card card--danger">
<h3 class="t-card-title">Delete Crawler</h3>
<p class="t-description">This crawler and all associated data will be lost</p>
<button class="btn btn--danger" style="margin-top: 16px;">Delete this crawler</button>
</article>
<!-- The confirm dialog opened by that button -->
<div class="dialog-backdrop">
<div class="dialog" role="dialog" aria-labelledby="dlg-title">
<div style="display: flex; justify-content: space-between;">
<h2 class="dialog__title" id="dlg-title">Delete crawler</h2>
<button class="btn btn--ghost btn--small" aria-label="Close">×</button>
</div>
<p class="t-body">
Are you sure you want to delete this crawler? This action is irreversible.
We will delete your crawler, the configuration, all pending operations, all logs.
</p>
<p class="t-body" style="margin-top: 12px;">
<strong>We will not</strong> remove anything from your Algolia account. If you want to delete the
associated Algolia indices, you will need to do it by hand afterward.
</p>
<p class="t-description" style="margin-top: 20px;">Enter "DELETE" to confirm.</p>
<input class="input" type="text" id="confirm-input" autocomplete="off">
<div class="dialog__actions">
<button class="btn btn--danger" disabled id="confirm-delete">Delete</button>
</div>
</div>
</div>
<script>
document.querySelector('#confirm-input').addEventListener('input', e => {
document.querySelector('#confirm-delete').disabled = e.target.value !== 'DELETE';
});
</script>
Rules: - The scope statement (what is not affected) is mandatory. - The button stays disabled until the user types "DELETE" exactly. - No second "Cancel" button needed — closing the dialog (X or Escape) is the cancel.
11. Status pill
<span class="pill pill--success">Success</span>
<span class="pill pill--warning">Canceled</span>
<span class="pill pill--neutral">Created</span>
<span class="pill pill--running">
<span style="width: 6px; height: 6px; border-radius: 50%; background: currentColor;"></span>
Running
</span>
<span class="pill pill--danger">Failed</span>
12. KPI tile row
<div class="kpi-row">
<article class="kpi">
<div class="kpi__value">67</div>
<div class="kpi__label">URLs contained in this path</div>
<span class="kpi__icon"><svg>…globe…</svg></span>
</article>
<article class="kpi">
<div class="kpi__value">11</div>
<div class="kpi__label">Records extracted from this path</div>
<span class="kpi__icon"><svg>…database…</svg></span>
</article>
<article class="kpi">
<div class="kpi__value">0</div>
<div class="kpi__label">URLs that failed within this path</div>
<span class="kpi__icon"><svg>…x-circle…</svg></span>
</article>
<article class="kpi">
<div class="kpi__value">5</div>
<div class="kpi__label">URLs that were skipped within this path</div>
<span class="kpi__icon"><svg>…ban…</svg></span>
</article>
</div>
13. Empty states
Variant: data (table empty)
<div class="empty">
<div class="t-card-title" style="color: var(--ink-secondary);">Empty</div>
<p class="t-description">There is no data to display</p>
</div>
Variant: gated (requires-prereq)
<div class="empty empty--gated">
<svg width="80" height="80" aria-hidden="true">
<!-- shield illustration -->
</svg>
<h2 class="empty__title">You need to enable Crawler Logs first</h2>
<p class="t-description" style="max-width: 480px;">
To use Logs Explorer, you must first enable the Crawler Logs, then generate
data by running a complete crawl of your domain.
</p>
<p class="t-description">
<a href="…">Learn more ↗</a> about what's involved.
</p>
<button class="btn btn--primary">Go to Configuration</button>
</div>
Variant: edit-empty (inside an edit-mode card)
<p class="t-description" style="text-align: center; padding: 24px;">
No markdown indices created yet. Click "Add Index" to create your first one.
</p>
<button class="btn btn--ghost" style="align-self: flex-start;">+ Add Index</button>
14. Page header with tabs
<header style="margin-bottom: 24px;">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div>
<h1 class="t-h1">Crawler Configuration</h1>
<p class="t-description" style="margin-top: 8px; max-width: 720px;">
Customise how your crawler navigates and processes your site, ensuring efficient data
collection while maintaining control over performance and security.
For more advanced configurations, use the <a href="…">Crawler Code Editor</a> instead.
</p>
</div>
<!-- optional secondary control on the right (e.g., index dropdown for Data Analysis) -->
</div>
<nav class="tabs" role="tablist" style="margin-top: 24px;">
<button class="tab" role="tab" aria-selected="true">Initial Set-up</button>
<button class="tab" role="tab" aria-selected="false">Markdown for LLMs</button>
<button class="tab" role="tab" aria-selected="false">Safety Checks & Performance</button>
<button class="tab" role="tab" aria-selected="false">Security & Data Management</button>
</nav>
</header>
15. Three-section left nav
<nav class="left-rail" aria-label="Crawler navigation">
<section class="nav-section">
<header class="nav-section__header">
<svg width="14" height="14">…trend…</svg>
Setup
<button aria-label="Collapse" style="margin-left: auto;">▾</button>
</header>
<ul class="nav-section__list">
<li><a class="nav-item" href="…">Overview</a></li>
<li><a class="nav-item" href="…" aria-current="true">Configuration</a></li>
<li><a class="nav-item" href="…">Editor</a></li>
<li><a class="nav-item" href="…">Suggestions</a></li>
</ul>
</section>
<section class="nav-section">
<header class="nav-section__header">
<svg width="14" height="14">…wrench…</svg>
Status
<button aria-label="Collapse" style="margin-left: auto;">▾</button>
</header>
<ul class="nav-section__list">
<li><a class="nav-item" href="…">URL inspector</a></li>
<li><a class="nav-item" href="…">Monitoring</a></li>
<li><a class="nav-item" href="…">Data Analysis</a></li>
<li><a class="nav-item" href="…">Path Explorer</a></li>
<li><a class="nav-item" href="…">Logs Explorer</a></li>
</ul>
</section>
<section class="nav-section">
<header class="nav-section__header">
<svg width="14" height="14">…gear…</svg>
Configuration
<button aria-label="Collapse" style="margin-left: auto;">▾</button>
</header>
<ul class="nav-section__list">
<li><a class="nav-item" href="…">External Data</a></li>
<li><a class="nav-item" href="…">Settings</a></li>
</ul>
</section>
</nav>
16. Persistent CTA top bar
<div class="workspace-topbar">
<!-- Left: module wordmark -->
<div style="display: flex; align-items: center; gap: 12px;">
<span style="width: 12px; height: 12px; border-radius: 50%; background: #0FBE6E;"></span>
<span style="font-weight: 700; letter-spacing: 0.18em;">CRAWLER</span>
</div>
<!-- Center: scope indicator -->
<div style="display: flex; flex-direction: column; align-items: center; line-height: 1.2;">
<span class="t-description">Crawler</span>
<button style="display: flex; align-items: center; gap: 8px; background: none; border: none;">
<span class="avatar-square" style="background: orange; width: 20px; height: 20px; border-radius: 4px;"></span>
<span style="font-weight: 600;">Wellington</span>
<svg width="12" height="12">▾</svg>
</button>
</div>
<!-- Right: persistent CTA + close -->
<div style="display: flex; gap: 8px; align-items: center;">
<button class="btn btn--primary">Start Crawling</button>
<button class="btn btn--ghost btn--small" aria-label="Close and return to Algolia">×</button>
</div>
</div>
17. Toolbar above table
<div style="display: flex; gap: 12px; align-items: center; margin-bottom: 16px;">
<input class="input" type="search" placeholder="Search…" style="max-width: 320px;">
<select class="input" style="width: 160px;"><option>All Status</option></select>
<div style="margin-left: auto; display: flex; gap: 8px;">
<button class="btn btn--ghost btn--small">Recrawl failed URLs</button>
<button class="btn btn--primary btn--small">Add new crawler</button>
</div>
</div>
18. Data table
<table style="width: 100%; border-collapse: separate; border-spacing: 0;">
<thead>
<tr style="border-bottom: 1px solid var(--line-default);">
<th style="text-align: left; padding: 12px 16px; font-size: 13px; color: var(--ink-secondary); font-weight: 400;"><input type="checkbox"></th>
<th style="text-align: left; padding: 12px 16px; font-size: 13px; color: var(--ink-secondary); font-weight: 400;">Name</th>
<th style="text-align: left; padding: 12px 16px; font-size: 13px; color: var(--ink-secondary); font-weight: 400;">Status</th>
<th style="text-align: left; padding: 12px 16px; font-size: 13px; color: var(--ink-secondary); font-weight: 400;">Author</th>
<th style="text-align: left; padding: 12px 16px; font-size: 13px; color: var(--ink-secondary); font-weight: 400;">Last indexed</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom: 1px solid var(--line-default);">
<td style="padding: 14px 16px;"><input type="checkbox"></td>
<td style="padding: 14px 16px;"><a href="…">Wellington</a></td>
<td style="padding: 14px 16px;"><span class="pill pill--warning">Canceled</span></td>
<td style="padding: 14px 16px; display: flex; align-items: center; gap: 8px;">
<span class="avatar-circle">SM</span>
sajid.momin@algolia.com
</td>
<td style="padding: 14px 16px;">3 days ago</td>
</tr>
</tbody>
</table>
19. Info banner inline
<div class="info-banner">
<svg width="16" height="16" aria-hidden="true">…info…</svg>
<div>
The crawler won't update your records until you check and confirm the change is okay.
<a href="…">Learn more ↗</a> about what's involved.
</div>
</div>
Use for: safety rails on consequential actions, prerequisite reminders, scope clarifications.
20. Suggestion card
<article class="card">
<header style="display: flex; justify-content: space-between; align-items: center;">
<h3 class="t-card-title">Website HTML contains redirect to a different domain</h3>
<div style="display: flex; gap: 8px;">
<button class="btn btn--ghost btn--small">Snooze</button>
<button class="btn btn--ghost btn--small">Dismiss</button>
<a class="btn btn--ghost btn--small" href="…">Docs ↗</a>
</div>
</header>
<dl style="margin-top: 16px; display: grid; grid-template-columns: 120px 1fr; gap: 12px 16px;">
<dt class="t-description"><strong>Description</strong></dt>
<dd class="t-body">A crawler might follow a URL redirect but won't crawl the new page. <a href="…">Add any missing domains ↗</a> and update <code>pathsToMatch</code> in the crawler configuration.</dd>
<dt class="t-description"><strong>Solution</strong></dt>
<dd><a href="…">View in Monitoring</a></dd>
</dl>
</article>
21. Grouped warnings (Data Analysis post-run)
<!-- Header banner with index name + Restart action -->
<div class="card" style="display: flex; justify-content: space-between; align-items: center; background: var(--surface-subtle); border: none;">
<div>
<h3 class="t-card-title">crawler_Wellington-Funds</h3>
<p class="t-description">Last analyzed less than a minute ago</p>
</div>
<button class="btn btn--ghost btn--small">↻ Restart analysis</button>
</div>
<!-- Tabs -->
<nav class="tabs" style="margin-top: 24px;">
<button class="tab" aria-selected="true">Warnings <span class="tab__count">21</span></button>
<button class="tab" aria-selected="false">Dismissed warnings <span class="tab__count">0</span></button>
</nav>
<!-- Group: per-attribute -->
<section style="margin-top: 24px;">
<h4 class="t-card-title">description</h4>
<p class="t-description">
<a href="…">Affects 3 records out of 13 records (23.08%)</a>
</p>
<table style="width: 100%; margin-top: 12px;">
<thead>
<tr><th>Warning</th><th>Records affected</th><th>Preview</th><th>Dismiss Warning</th></tr>
</thead>
<tbody>
<tr>
<td>Missing Data</td>
<td>3</td>
<td><button class="btn btn--ghost btn--small">View URLs</button></td>
<td><a href="#">× Dismiss</a></td>
</tr>
<tr>
<td>Healthy Records</td>
<td>10</td>
<td><button class="btn btn--ghost btn--small">View URLs</button></td>
<td></td>
</tr>
</tbody>
</table>
</section>
<!-- Repeat per attribute group -->
22. Run-control cluster
The persistent top-right control of the focused workspace. State-aware: button label and overflow menu both change based on the crawler's current run state. This is our value-add over Algolia's single static CTA.
<!-- State: running -->
<div class="run-control" data-state="running" style="display: flex; gap: 8px; align-items: center;">
<button class="btn btn--ghost" id="pause-btn">
<svg width="14" height="14" aria-hidden="true">…pause icon…</svg>
Pause
</button>
<div class="run-control__menu" style="position: relative;">
<button class="btn btn--ghost btn--small" aria-haspopup="menu" aria-expanded="false" aria-label="More run actions">
<svg width="14" height="14">⋯</svg>
</button>
<div role="menu" hidden style="position: absolute; right: 0; top: 100%; margin-top: 4px; background: var(--surface-elevated); border: 1px solid var(--line-default); border-radius: 8px; box-shadow: var(--shadow-popover); min-width: 200px; padding: 4px;">
<button role="menuitem" class="run-control__item">Stop crawl</button>
<button role="menuitem" class="run-control__item">View live log</button>
<hr style="border: 0; border-top: 1px solid var(--line-default); margin: 4px 0;">
<button role="menuitem" class="run-control__item">Settings</button>
</div>
</div>
<button class="btn btn--ghost btn--small" aria-label="Close and return to dashboard">×</button>
</div>
<style>
.run-control__item {
display: block; width: 100%; text-align: left;
padding: 8px 12px; font-size: var(--text-body);
background: none; border: 0; border-radius: 6px;
cursor: pointer; color: var(--ink-primary);
}
.run-control__item:hover { background: var(--surface-subtle); }
.run-control__item--danger { color: var(--status-danger-fg); }
</style>
Stateful labels:
| State | Primary button | Overflow items |
|---|---|---|
idle |
Crawl now (filled) |
Edit configuration · Re-crawl from scratch · Settings |
running |
Pause (ghost) |
Stop crawl · View live log · Settings |
paused |
Resume (filled) |
Stop crawl · Edit configuration · Re-crawl from scratch |
stopped |
Crawl now (filled) |
Re-crawl from scratch · Edit configuration · Settings |
failed |
Retry (filled) |
View error log · Edit configuration · Settings |
Implementation notes:
- The button visually morphs (label + icon) between states, never a layout shift.
- Overflow menu is keyboard-navigable: ↑/↓ between items, Escape to close, Enter to activate.
- Menu items that lead to destructive actions (Stop crawl, Re-crawl from scratch) get the --danger modifier on hover state.
- Re-crawl from scratch always opens Recipe 24 — never fires immediately.
23. Run-state pill (list row)
Used in the crawler list table to show the current state of each crawler at a glance. Different from the static "last status" pills (Recipe 11): this one is live and it animates only the dot, never the layout.
<!-- Idle -->
<span class="pill pill--neutral">
<svg width="6" height="6"><circle cx="3" cy="3" r="3" fill="currentColor"/></svg>
Idle
</span>
<!-- Running -->
<span class="pill pill--running run-state-pill--live">
<svg width="6" height="6"><circle cx="3" cy="3" r="3" fill="currentColor"/></svg>
Running
</span>
<!-- Paused -->
<span class="pill pill--warning">
<svg width="10" height="10" aria-hidden="true">…pause-bars…</svg>
Paused
</span>
<!-- Stopped (user stopped) -->
<span class="pill pill--neutral">Stopped</span>
<!-- Failed -->
<span class="pill pill--danger">Failed</span>
<!-- Done (last run completed) -->
<span class="pill pill--success">
<svg width="10" height="10" aria-hidden="true">…check…</svg>
Done
</span>
<style>
/* The only animation in the entire pack — the live dot.
Restraint: 1.6s ease-in-out, no scale, opacity only. */
.run-state-pill--live svg circle {
animation: livePulse 1.6s ease-in-out infinite;
}
@keyframes livePulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.35; }
}
</style>
Rules:
- Only Running gets the live-dot pulse. All other states are static.
- Pulse is opacity-only; never use scale, glow, or color shifts.
- The pill is paired with a 3-dot overflow menu in the row to expose actions (Pause / Resume / Stop / Open / Delete).
24. Re-crawl confirm dialog
A scoped confirmation — less heavy than the typed-confirm (Recipe 10) because the action is destructive of records only, not the asset itself. Records get re-built on the next crawl. The crawler config, schedule, and index name all survive.
<div class="dialog-backdrop">
<div class="dialog" role="dialog" aria-labelledby="recrawl-title">
<div style="display: flex; justify-content: space-between;">
<h2 class="dialog__title" id="recrawl-title">Re-crawl from scratch?</h2>
<button class="btn btn--ghost btn--small" aria-label="Close">×</button>
</div>
<p class="t-body">
We'll wipe the current index records and start a fresh crawl. The crawler's
configuration, schedule, and index name will not change.
</p>
<div class="info-banner" style="margin-top: 16px;">
<svg width="16" height="16">…info…</svg>
<div>
Search will return <strong>no results</strong> for this crawler until the
first records are indexed (typically within a minute for small sites).
</div>
</div>
<div class="dialog__actions">
<button class="btn btn--ghost">Cancel</button>
<button class="btn btn--primary">Re-crawl now</button>
</div>
</div>
</div>
Why not typed-confirm? Re-crawl is recoverable — the next crawl rebuilds the records. Typed-confirm (Recipe 10) is reserved for irreversible actions (Delete crawler). Reserving it for true irreversibility is what makes it credible.
How to extend this file
When you encounter a pattern in the dashboard not covered here:
- Take a screenshot, save it under
dashboard_screenshots/with a numbered name. - Add a finding to
00-findings.mddescribing what's new. - Add a recipe to this file with the copy-paste skeleton.
- If the pattern requires a new token, add it to
tokens.cssand reference it in01-design-language.md§1.
The pack stays portable as long as all four files travel together: 00-findings.md, 01-design-language.md, 02-component-recipes.md, tokens.css.