diff --git a/ui/src/components/NewIssueDialog.tsx b/ui/src/components/NewIssueDialog.tsx
index 2881a7dc..95d30d2b 100644
--- a/ui/src/components/NewIssueDialog.tsx
+++ b/ui/src/components/NewIssueDialog.tsx
@@ -24,6 +24,7 @@ import {
DialogContent,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
+import { ToggleSwitch } from "@/components/ui/toggle-switch";
import {
Popover,
PopoverContent,
@@ -1208,21 +1209,10 @@ export function NewIssueDialog() {
{assigneeAdapterType === "claude_local" && (
Enable Chrome (--chrome)
-
+
setAssigneeChrome((value) => !value)}
+ />
)}
diff --git a/ui/src/components/ProjectProperties.tsx b/ui/src/components/ProjectProperties.tsx
index 6574091c..a13eb3cc 100644
--- a/ui/src/components/ProjectProperties.tsx
+++ b/ui/src/components/ProjectProperties.tsx
@@ -16,6 +16,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { AlertCircle, Archive, ArchiveRestore, Check, ExternalLink, Github, Loader2, Plus, Trash2, X } from "lucide-react";
import { ChoosePathButton } from "./PathInstructionsModal";
+import { ToggleSwitch } from "@/components/ui/toggle-switch";
import { DraftInput } from "./agent-config-primitives";
import { InlineEditor } from "./InlineEditor";
@@ -886,26 +887,14 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
{onUpdate || onFieldUpdate ? (
-
+ />
) : (
{executionWorkspacesEnabled ? "Enabled" : "Disabled"}
@@ -925,14 +914,9 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
If disabled, new issues stay on the project's primary checkout unless someone opts in.
-
+ />
diff --git a/ui/src/components/agent-config-primitives.tsx b/ui/src/components/agent-config-primitives.tsx
index 76b3aef0..5c4c1491 100644
--- a/ui/src/components/agent-config-primitives.tsx
+++ b/ui/src/components/agent-config-primitives.tsx
@@ -4,6 +4,7 @@ import {
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
+import { ToggleSwitch } from "@/components/ui/toggle-switch";
import {
Dialog,
DialogContent,
@@ -111,23 +112,11 @@ export function ToggleField({
{label}
{hint && }
-
+ />
);
}
@@ -162,21 +151,10 @@ export function ToggleWithNumber({
{label}
{hint && }
-
+
{showNumber && (
diff --git a/ui/src/components/ui/toggle-switch.tsx b/ui/src/components/ui/toggle-switch.tsx
new file mode 100644
index 00000000..d4995f25
--- /dev/null
+++ b/ui/src/components/ui/toggle-switch.tsx
@@ -0,0 +1,59 @@
+import * as React from "react";
+import { cn } from "@/lib/utils";
+
+export interface ToggleSwitchProps
+ extends Omit, "onChange"> {
+ checked: boolean;
+ onCheckedChange: (checked: boolean) => void;
+ size?: "default" | "lg";
+}
+
+export const ToggleSwitch = React.forwardRef<
+ HTMLButtonElement,
+ ToggleSwitchProps
+>(
+ (
+ { checked, onCheckedChange, size = "default", className, disabled, ...props },
+ ref,
+ ) => {
+ const isLg = size === "lg";
+
+ return (
+
+ );
+ },
+);
+
+ToggleSwitch.displayName = "ToggleSwitch";
diff --git a/ui/src/pages/AgentDetail.tsx b/ui/src/pages/AgentDetail.tsx
index 40d342c7..caa90578 100644
--- a/ui/src/pages/AgentDetail.tsx
+++ b/ui/src/pages/AgentDetail.tsx
@@ -25,6 +25,7 @@ import { queryKeys } from "../lib/queryKeys";
import { AgentConfigForm } from "../components/AgentConfigForm";
import { PageTabBar } from "../components/PageTabBar";
import { adapterLabels, roleLabels, help } from "../components/agent-config-primitives";
+import { ToggleSwitch } from "@/components/ui/toggle-switch";
import { MarkdownEditor } from "../components/MarkdownEditor";
import { assetsApi } from "../api/assets";
import { getUIAdapter, buildTranscript, onAdapterChange } from "../adapters";
@@ -1627,30 +1628,16 @@ function ConfigurationTab({
Lets this agent create or hire agents and implicitly assign tasks.
-
+ />
@@ -1659,30 +1646,16 @@ function ConfigurationTab({
{taskAssignHint}
-
+ />
diff --git a/ui/src/pages/InstanceExperimentalSettings.tsx b/ui/src/pages/InstanceExperimentalSettings.tsx
index 050166ff..753ab5fd 100644
--- a/ui/src/pages/InstanceExperimentalSettings.tsx
+++ b/ui/src/pages/InstanceExperimentalSettings.tsx
@@ -4,7 +4,7 @@ import { FlaskConical } from "lucide-react";
import { instanceSettingsApi } from "@/api/instanceSettings";
import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { queryKeys } from "../lib/queryKeys";
-import { cn } from "../lib/utils";
+import { ToggleSwitch } from "@/components/ui/toggle-switch";
export function InstanceExperimentalSettings() {
const { setBreadcrumbs } = useBreadcrumbs();
@@ -82,24 +82,12 @@ export function InstanceExperimentalSettings() {
and existing issue runs.
-
+ aria-label="Toggle isolated workspaces experimental setting"
+ />
@@ -112,26 +100,12 @@ export function InstanceExperimentalSettings() {
automatically when backend changes or migrations make the current boot stale.
-
+ aria-label="Toggle guarded dev-server auto-restart"
+ />
diff --git a/ui/src/pages/InstanceGeneralSettings.tsx b/ui/src/pages/InstanceGeneralSettings.tsx
index 923c8cb8..28e00b29 100644
--- a/ui/src/pages/InstanceGeneralSettings.tsx
+++ b/ui/src/pages/InstanceGeneralSettings.tsx
@@ -7,6 +7,7 @@ import { instanceSettingsApi } from "@/api/instanceSettings";
import { Button } from "../components/ui/button";
import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { queryKeys } from "../lib/queryKeys";
+import { ToggleSwitch } from "@/components/ui/toggle-switch";
import { cn } from "../lib/utils";
const FEEDBACK_TERMS_URL = import.meta.env.VITE_FEEDBACK_TERMS_URL?.trim() || "https://paperclip.ing/tos";
@@ -95,28 +96,12 @@ export function InstanceGeneralSettings() {
default.
-
+ aria-label="Toggle username log censoring"
+ />
@@ -129,24 +114,12 @@ export function InstanceGeneralSettings() {
toggling panels. This is off by default.
-
+ aria-label="Toggle keyboard shortcuts"
+ />
diff --git a/ui/src/pages/RoutineDetail.tsx b/ui/src/pages/RoutineDetail.tsx
index 55dc32f4..c1ca92e7 100644
--- a/ui/src/pages/RoutineDetail.tsx
+++ b/ui/src/pages/RoutineDetail.tsx
@@ -27,6 +27,7 @@ import { useToast } from "../context/ToastContext";
import { queryKeys } from "../lib/queryKeys";
import { buildRoutineTriggerPatch } from "../lib/routine-trigger-patch";
import { timeAgo } from "../lib/timeAgo";
+import { ToggleSwitch } from "@/components/ui/toggle-switch";
import { EmptyState } from "../components/EmptyState";
import { PageSkeleton } from "../components/PageSkeleton";
import { AgentIcon } from "../components/AgentIconPicker";
@@ -710,24 +711,13 @@ export function RoutineDetail() {
}}
disabled={runRoutine.isPending}
/>
-
+ aria-label={automationEnabled ? "Pause automatic triggers" : "Enable automatic triggers"}
+ />
{automationLabel}
diff --git a/ui/src/pages/Routines.tsx b/ui/src/pages/Routines.tsx
index fc856d72..b58cea8c 100644
--- a/ui/src/pages/Routines.tsx
+++ b/ui/src/pages/Routines.tsx
@@ -11,6 +11,7 @@ import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { useToast } from "../context/ToastContext";
import { queryKeys } from "../lib/queryKeys";
import { getRecentAssigneeIds, sortAgentsByRecency, trackRecentAssignee } from "../lib/recent-assignees";
+import { ToggleSwitch } from "@/components/ui/toggle-switch";
import { EmptyState } from "../components/EmptyState";
import { PageSkeleton } from "../components/PageSkeleton";
import { AgentIcon } from "../components/AgentIconPicker";
@@ -640,29 +641,18 @@ export function Routines() {
e.stopPropagation()}>
-
+ disabled={isStatusPending || isArchived}
+ aria-label={enabled ? `Disable ${routine.title}` : `Enable ${routine.title}`}
+ />
{isArchived ? "Archived" : enabled ? "On" : "Off"}
|