Component
Checkbox Readonly
Read-only checkbox that stays focusable but cannot be toggled by the user.
- Component
- React
- TSX
- Tailwind CSS
- MIT
What it does
Capabilities
- Semantic HTML
- Native form control
- Native checkbox
- Associated labels
- ARIA
Preview
Live component
9:41 100%
devsnips.site/library/react/components/checkboxes/checkbox-readonly/ fluid · no fixed width 100%
Install
Add to your project
npx devsnips add React/Components/Checkboxes/checkbox-readonly React/Components/Checkboxes/checkbox-readonly import type { ChangeEvent, ReactNode } from "react";
import { useId, useState } from "react";
function cx(...parts: Array<string | false | null | undefined>): string {
return parts.filter(Boolean).join(" ");
}
export interface CheckboxReadonlyProps {
label: ReactNode;
checked: boolean;
onChange?: (checked: boolean, event: ChangeEvent<HTMLInputElement>) => void;
name?: string;
value?: string | number | readonly string[];
id?: string;
helperText?: ReactNode;
className?: string;
}
/**
* Read-only checkbox. The control cannot be changed by the user but is NOT
* disabled — it remains focusable and perceivable as part of the document
* (e.g. a permission that is fixed). The native input uses `readOnly` plus a
* `preventDefault` on change (browsers do not natively honor `readOnly` on
* checkboxes), so clicks and Space do not toggle the value.
*/
export function CheckboxReadonly({
label,
checked,
onChange,
name,
value,
id,
helperText,
className,
}: CheckboxReadonlyProps) {
const generatedId = useId();
const inputId = id ?? `checkbox-${generatedId}`;
const helperId = `${inputId}-helper`;
function handleChange(event: ChangeEvent<HTMLInputElement>) {
// Browsers do not honor `readOnly` on checkboxes — force the rendered
// state back so it cannot be toggled. The onChange callback still fires so
// a parent can log the (blocked) attempt if it wants.
event.preventDefault();
onChange?.(checked, event);
}
return (
<div className={cx("flex flex-col gap-1", className)}>
<label
htmlFor={inputId}
className="inline-flex cursor-default items-center gap-2.5 text-sm leading-5 text-[var(--ds-color-foreground)]"
>
<span className="relative inline-flex size-[18px] shrink-0 items-center justify-center">
<input
id={inputId}
type="checkbox"
readOnly
aria-readonly="true"
aria-describedby={helperText ? helperId : undefined}
className="size-[18px] cursor-default appearance-none rounded-[var(--ds-radius-xs)] border border-[var(--ds-color-border)] bg-[var(--ds-color-surface-subtle)] read-only:text-[var(--ds-color-muted-foreground)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-color-focus-ring)] checked:border-[var(--ds-color-muted-foreground)] checked:bg-[var(--ds-color-muted-foreground)] motion-reduce:transition-none"
checked={checked}
name={name}
value={value}
onChange={handleChange}
/>
<span
aria-hidden="true"
className={cx(
"pointer-events-none absolute inset-0 flex items-center justify-center text-[var(--ds-color-primary-foreground)] transition-opacity duration-150 motion-reduce:transition-none",
checked ? "opacity-100" : "opacity-0",
)}
>
<svg className="size-[12px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={3.5} strokeLinecap="round" strokeLinejoin="round">
<path d="M20 6 9 17l-5-5" />
</svg>
</span>
</span>
<span className="select-none">{label}</span>
</label>
{helperText ? (
<p id={helperId} className="pl-[26px] text-xs leading-4 text-[var(--ds-color-muted-foreground)]">
{helperText}
</p>
) : null}
</div>
);
}
export default CheckboxReadonly; /* DevSnips React — JavaScript parity build.
* Same API, behavior, and classes as code.tsx; TypeScript types removed.
* Regenerated from code.tsx — edit code.tsx and re-run the generator.
*/
import { useId } from "react";
function cx(...parts) {
return parts.filter(Boolean).join(" ");
}
export function CheckboxReadonly({
label,
checked,
onChange,
name,
value,
id,
helperText,
className
}) {
const generatedId = useId();
const inputId = id ?? `checkbox-${generatedId}`;
const helperId = `${inputId}-helper`;
function handleChange(event) {
event.preventDefault();
onChange?.(checked, event);
}
return <div className={cx("flex flex-col gap-1", className)}>
<label
htmlFor={inputId}
className="inline-flex cursor-default items-center gap-2.5 text-sm leading-5 text-[var(--ds-color-foreground)]"
>
<span className="relative inline-flex size-[18px] shrink-0 items-center justify-center">
<input
id={inputId}
type="checkbox"
readOnly
aria-readonly="true"
aria-describedby={helperText ? helperId : undefined}
className="size-[18px] cursor-default appearance-none rounded-[var(--ds-radius-xs)] border border-[var(--ds-color-border)] bg-[var(--ds-color-surface-subtle)] read-only:text-[var(--ds-color-muted-foreground)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-color-focus-ring)] checked:border-[var(--ds-color-muted-foreground)] checked:bg-[var(--ds-color-muted-foreground)] motion-reduce:transition-none"
checked={checked}
name={name}
value={value}
onChange={handleChange}
/>
<span
aria-hidden="true"
className={cx(
"pointer-events-none absolute inset-0 flex items-center justify-center text-[var(--ds-color-primary-foreground)] transition-opacity duration-150 motion-reduce:transition-none",
checked ? "opacity-100" : "opacity-0"
)}
>
<svg className="size-[12px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={3.5} strokeLinecap="round" strokeLinejoin="round">
<path d="M20 6 9 17l-5-5" />
</svg>
</span>
</span>
<span className="select-none">{label}</span>
</label>
{helperText ? <p id={helperId} className="pl-[26px] text-xs leading-4 text-[var(--ds-color-muted-foreground)]">
{helperText}
</p> : null}
</div>;
}
export default CheckboxReadonly; # Checkbox Readonly
Read-only checkbox that stays focusable but cannot be toggled by the user.
## Usage
```tsx
<CheckboxReadonly label="System-managed permission" checked />
```
## JavaScript
A `code.jsx` build is provided for projects that ship plain JSX. It exposes the same API and behavior as `code.tsx` — only the TypeScript types are removed.
```jsx
<CheckboxReadonly label="System-managed permission" checked />
```
## Props
| Name | Type | Default | Description |
|---|---|---:|---|
| `label` | `ReactNode` (required) | — | Visible label. |
| `checked` | `boolean` (required) | — | Fixed checked state. |
| `onChange` | `(checked, event) => void` | — | Fires with the blocked attempt. |
| `helperText` | `ReactNode` | — | Helper text. |
| `name` / `value` / `id` | `string` | — | Native form attrs. |
## States
Read-only checkbox. The native input uses `readOnly` plus a `preventDefault` on change (browsers do not natively honor `readOnly` on checkboxes), so clicks and Space do not toggle the value. Unlike `disabled`, it remains focusable and is still part of the document flow.
## Accessibility
Native `readOnly` + `aria-readonly="true"`. The control is focusable so users can read its state; `preventDefault` keeps the value fixed. Visible `focus-visible` ring.
## Styling
Built with React, Tailwind CSS, and DevSnips design tokens. The component consumes the `--ds-*` semantic tokens via arbitrary values (for example `bg-[var(--ds-color-primary)]`). Define the tokens once in your theme — no component-specific CSS file is required.
## Design Tokens
See [React/DESIGN_TOKENS.md](../../../DESIGN_TOKENS.md) for the authoritative token specification. This checkbox uses the semantic color, radius, spacing, and motion tokens.
## Notes
Use this for a value that is fixed in this context but should still be focusable and perceivable (e.g. a system-managed permission). Use `checkbox-disabled` when the option is genuinely non-interactive. 90 lines UTF-8 · LF · Spaces: 2
DevSnips AI ready Claude · Cursor · Windsurf
Agent skill & Model Context Protocol
Pass this component straight into your AI workflow. DevSnips exposes each resource as a structured, addressable registry entry, so your agent installs the real files instead of approximating them.
devsnips://components/checkboxes/checkbox-readonly I want to use this DevSnips resource in production: Page: https://devsnips.site/library/react/components/checkboxes/checkbox-readonly/ Install: npx devsnips add React/Components/Checkboxes/checkbox-readonly This is the Checkbox Readonly — Read-only checkbox that stays focusable but cannot be toggled by the user. Use this DevSnips resource as the starting point for my project. Adapt it to my project's stack and conventions, fix any issues, and make it production-ready while preserving its intended design and behavior.
Paste into ChatGPT, Claude or your agent — it already carries the page URL and the exact install path.
Resource URI
devsnips://components/checkboxes/checkbox-readonly