diff --git a/docs/web-ui/design-guide.md b/docs/web-ui/design-guide.md
index 654a435e..ea367054 100644
--- a/docs/web-ui/design-guide.md
+++ b/docs/web-ui/design-guide.md
@@ -150,9 +150,11 @@ that needs it, not after a contributor styles it inline and leaves it
for later — a page reaching for a component that's already there has ready-made
blocks to build with, instead of the next contributor duplicating
ad-hoc styling that someone then has to hunt down and consolidate.
-`Panel`/`Badge`/`Table`/`TextField`/`SelectField`/`Button` are the
-primitives that exist; `/components` always has the current, complete
-list — this doc won't try to keep a duplicate inventory in sync.
+`Panel`/`Badge`/`Table`/`TextField`/`SelectField`/`CheckboxField`/`Button`
+are the primitives that exist; `/components` always has the current, complete
+list — this doc won't try to keep a duplicate inventory in sync. A yes/no
+option in a form uses `CheckboxField`, which sizes its row like the other
+fields, rather than a bare ``.
**Every new `ui/` component gets a demo section on `/components` the
same day it lands** — no primitive without a place to see it. One
diff --git a/frontend/packages/swarm-ui/src/agent/detail/LinkedAccounts.tsx b/frontend/packages/swarm-ui/src/agent/detail/LinkedAccounts.tsx
index fa1eaf25..e9cee640 100644
--- a/frontend/packages/swarm-ui/src/agent/detail/LinkedAccounts.tsx
+++ b/frontend/packages/swarm-ui/src/agent/detail/LinkedAccounts.tsx
@@ -13,6 +13,7 @@ import { useEffect, useState } from "preact/hooks";
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
import { Badge } from "@hive/shared/badge.js";
+import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js";
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
import { accountKindLabel } from "./account-kind.js";
import "./LinkedAccounts.css";
@@ -136,17 +137,12 @@ export function LinkedAccounts({
removed from the swarm secret store. The agent is not told.
{BUTTON_VARIANTS.map((variant) => (
diff --git a/frontend/packages/swarm-ui/src/pages/IssueReportPage.css b/frontend/packages/swarm-ui/src/pages/IssueReportPage.css
index b69e7b20..dd3a227e 100644
--- a/frontend/packages/swarm-ui/src/pages/IssueReportPage.css
+++ b/frontend/packages/swarm-ui/src/pages/IssueReportPage.css
@@ -15,12 +15,6 @@
gap: 1.25em;
margin-bottom: 1em;
}
-.issue-report-toggle {
- display: flex;
- align-items: center;
- gap: 0.4em;
- cursor: pointer;
-}
.issue-report-sort-btn {
background: none;
border: none;
diff --git a/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx b/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx
index 6d98772c..55430d18 100644
--- a/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx
+++ b/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx
@@ -33,6 +33,7 @@ import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
import { Badge } from "@hive/shared/badge.js";
import { useLocalSetting } from "@hive/shared/settings-storage.js";
import { Panel } from "../ui/panel/Panel.js";
+import { CheckboxField } from "../ui/checkbox-field/CheckboxField.js";
import { SelectField } from "../ui/select-field/SelectField.js";
import { Table, type TableColumn } from "../ui/table/Table.js";
import { ALL_REPOS, reportUrl, repoOptions } from "./issue-report-repo.js";
@@ -378,16 +379,12 @@ export function IssueReportPage() {
onChange={setRepoFilter}
options={repoOptions(repos, repoFilter)}
/>
-
+
{reposError ? (
— box and label on one row. `min-height` is the same
+ touch-target floor `.ui-form-control` sets (FormField.css has the
+ reasoning), so a checkbox beside a text or select field is as tall as it and
+ its label is centred on the same line. The box takes the accent colour from
+ the shared theme instead of the browser's blue. */
+.ui-checkbox-field {
+ display: flex;
+ align-items: center;
+ gap: 0.5em;
+ min-height: 2.75em;
+ cursor: pointer;
+ color: var(--fg);
+}
+.ui-checkbox-field input {
+ accent-color: var(--purple);
+ width: 1.1em;
+ height: 1.1em;
+ margin: 0;
+ flex: none;
+ cursor: inherit;
+}
+.ui-checkbox-field:has(input:disabled) {
+ color: var(--muted);
+ cursor: not-allowed;
+}
diff --git a/frontend/packages/swarm-ui/src/ui/checkbox-field/CheckboxField.tsx b/frontend/packages/swarm-ui/src/ui/checkbox-field/CheckboxField.tsx
new file mode 100644
index 00000000..791e72e2
--- /dev/null
+++ b/frontend/packages/swarm-ui/src/ui/checkbox-field/CheckboxField.tsx
@@ -0,0 +1,33 @@
+// — a labelled checkbox, the on/off sibling of `TextField` and
+// `SelectField`. The label sits beside the box, not above it, and the whole
+// row is one click target at the kit's touch-target height, so a checkbox next
+// to a `SelectField` lines up with it and a bare browser checkbox no longer
+// sets the row's size.
+import "./CheckboxField.css";
+
+export function CheckboxField({
+ id,
+ label,
+ checked,
+ onChange,
+ disabled,
+}: {
+ id: string;
+ label: string;
+ checked: boolean;
+ onChange: (checked: boolean) => void;
+ disabled?: boolean;
+}) {
+ return (
+
+ );
+}