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

Interactive preview
Open preview
9:41 100%
devsnips.site/library/react/components/checkboxes/checkbox-readonly/
fluid · no fixed width 100%
No dependencies Responsive · Light/dark · Focus-visible

Install

Add to your project

terminal
npx devsnips add React/Components/Checkboxes/checkbox-readonly
registry path: React/Components/Checkboxes/checkbox-readonly

Source

Implementation

code.tsx
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;
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
Prompt instruction
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