Answer sheet

No dependencies
Code format
React + Tailwind
const ROWS = [
  { q: '4', filled: 'B' },
  { q: '5', filled: 'D' },
];
const LETTERS = ['A', 'B', 'C', 'D'];
const SCRIBBLE = 'M4 5L31 3L4 8L32 6L3 11L33 9L3 14L33 12L4 17L32 15L5 20L30 18L8 22L28 21';

const css = `
  .cb-answer-sheet { --paper: light-dark(#ebedf0, #e5e6e9); --print: light-dark(#373a40, #121417); --soft: color-mix(in oklab, var(--print) 74%, var(--paper)); --graphite: color-mix(in oklab, var(--print) 86%, var(--paper)); --wood: light-dark(#6d7179, #7d8087); flex: none; position: relative; display: block; width: 244px; height: 222px; box-sizing: border-box; padding: 12px 14px 10px 30px; background: var(--paper); color: var(--print); font-family: 'Courier New', Courier, monospace; clip-path: polygon(0 0, calc(100% - 24px) 0, 100% 24px, 100% 100%, 0 100%); cursor: pointer; -webkit-user-select: none; user-select: none; }
  .cb-answer-sheet__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
  .cb-answer-sheet__marks { position: absolute; left: 9px; top: 12px; bottom: 12px; width: 11px; background: repeating-linear-gradient(to bottom, var(--print) 0 5px, transparent 5px 14px); }
  .cb-answer-sheet__head { display: block; padding-bottom: 6px; border-bottom: 2px solid var(--print); font: 700 10px/1.2 system-ui, sans-serif; letter-spacing: 0.1em; text-transform: uppercase; }
  .cb-answer-sheet__note { display: block; margin-top: 3px; font: 500 8.5px/1.2 system-ui, sans-serif; letter-spacing: 0.02em; }
  .cb-answer-sheet__row { display: flex; align-items: center; gap: 7px; height: 24px; font-size: 11px; font-weight: 700; }
  .cb-answer-sheet__row b { width: 16px; font-weight: 700; }
  .cb-answer-sheet__opt { display: grid; place-items: center; width: 24px; height: 14px; border: 1px solid var(--print); border-radius: 50%; font: 700 8px/1 system-ui, sans-serif; }
  .cb-answer-sheet__opt--in { background: var(--graphite); }
  .cb-answer-sheet__real { display: flex; align-items: center; gap: 9px; margin-top: 7px; padding: 8px 0; border-top: 1px solid var(--print); border-bottom: 1px solid var(--print); }
  .cb-answer-sheet__real b { font-size: 11px; font-weight: 700; }
  .cb-answer-sheet__oval { position: relative; flex: none; display: block; box-sizing: border-box; width: 44px; height: 26px; border: 1.5px solid var(--print); border-radius: 50%; }
  .cb-answer-sheet__input:focus-visible ~ .cb-answer-sheet__real .cb-answer-sheet__oval { outline: 2px solid var(--print); outline-offset: 2px; }
  .cb-answer-sheet__mark { position: absolute; left: 3px; top: 0; width: 38px; height: 26px; overflow: visible; }
  .cb-answer-sheet__lead { fill: none; stroke: var(--graphite); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 0.2s; }
  .cb-answer-sheet__input:checked ~ .cb-answer-sheet__real .cb-answer-sheet__lead { stroke-dashoffset: 0; transition: stroke-dashoffset 0.72s linear 0.14s; }
  .cb-answer-sheet__pencil { position: absolute; left: 0; top: 0; width: 86px; height: 14px; offset-path: path('M4 5L31 3L4 8L32 6L3 11L33 9L3 14L33 12L4 17L32 15L5 20L30 18L8 22L28 21'); offset-rotate: 0deg; offset-anchor: 0 100%; offset-distance: 0%; opacity: 0; transform: rotate(-38deg); transform-origin: 0 100%; pointer-events: none; }
  .cb-answer-sheet__input:checked ~ .cb-answer-sheet__real .cb-answer-sheet__pencil { animation: cb-answer-sheet-write 0.92s linear; }
  @keyframes cb-answer-sheet-write {
    0% { opacity: 0; offset-distance: 0%; transform: translate(14px, -18px) rotate(-38deg); }
    14% { opacity: 1; offset-distance: 0%; transform: rotate(-38deg); }
    90% { opacity: 1; offset-distance: 100%; transform: rotate(-38deg); }
    100% { opacity: 0; offset-distance: 100%; transform: translate(18px, -14px) rotate(-38deg); }
  }
  .cb-answer-sheet__wood { fill: var(--wood); }
  .cb-answer-sheet__tip { fill: var(--paper); stroke: var(--print); stroke-width: 0.7; }
  .cb-answer-sheet__lead-tip { fill: var(--print); }
  .cb-answer-sheet__band { fill: var(--print); }
  .cb-answer-sheet__text { min-width: 0; white-space: nowrap; font: 700 13px/1.15 Georgia, 'Times New Roman', serif; }
  .cb-answer-sheet__foot { position: absolute; left: 30px; right: 14px; bottom: 9px; display: flex; justify-content: space-between; font: 700 8px/1 'Courier New', Courier, monospace; letter-spacing: 0.06em; }
  @media (prefers-reduced-motion: reduce) {
    .cb-answer-sheet__lead, .cb-answer-sheet__input:checked ~ .cb-answer-sheet__real .cb-answer-sheet__lead { transition: none; }
    .cb-answer-sheet__input:checked ~ .cb-answer-sheet__real .cb-answer-sheet__pencil { animation: none; }
  }
`;

export function AnswerSheetCheckbox() {
  return (
    <>
      <style>
        {css}
      </style>
      <label className="cb-answer-sheet">
        <span className="cb-answer-sheet__marks" aria-hidden="true" />
        <span className="cb-answer-sheet__head" aria-hidden="true">
          Answer sheet
          <span className="cb-answer-sheet__note">Use a soft pencil. Fill the oval completely.</span>
        </span>
        <span aria-hidden="true">
          {ROWS.map((row) => (
            <span key={row.q} className="cb-answer-sheet__row">
              <b>{row.q}.</b>
              {LETTERS.map((l) => (
                <span key={l} className={`cb-answer-sheet__opt${row.filled === l ? ' cb-answer-sheet__opt--in' : ''}`}>
                  {row.filled === l ? '' : l}
                </span>
              ))}
            </span>
          ))}
        </span>
        <input className="cb-answer-sheet__input" type="checkbox" />
        <span className="cb-answer-sheet__real">
          <span className="cb-answer-sheet__oval" aria-hidden="true">
            <svg className="cb-answer-sheet__mark" viewBox="0 0 38 26">
              <path className="cb-answer-sheet__lead" pathLength={1} d={SCRIBBLE} />
            </svg>
            <svg className="cb-answer-sheet__pencil" viewBox="0 0 86 14">
              <path className="cb-answer-sheet__lead-tip" d="M0 12L3.5 9.5V13.5Z" />
              <path className="cb-answer-sheet__tip" d="M3 9.2L12 6V14L3 13.6Z" />
              <rect className="cb-answer-sheet__wood" x="12" y="6" width="58" height="8" />
              <rect className="cb-answer-sheet__band" x="70" y="6" width="5" height="8" />
              <rect className="cb-answer-sheet__tip" x="75" y="6" width="9" height="8" rx="2" />
            </svg>
          </span>
          <span className="cb-answer-sheet__text">6. I agree to the terms</span>
        </span>
        <span aria-hidden="true">
          <span className="cb-answer-sheet__row">
            <b>7.</b>
            {LETTERS.map((l) => (
              <span key={l} className="cb-answer-sheet__opt">
                {l}
              </span>
            ))}
          </span>
        </span>
        <span className="cb-answer-sheet__foot" aria-hidden="true">
          <span>FORM 22-B</span>
          <span>PAGE 3 OF 8</span>
        </span>
      </label>
    </>
  );
}

Free to use in any project, commercial included. MIT-0, no attribution required. Licence

Install with shadcn

npx shadcn@latest add @movin/checkbox-answer-sheet
Source on GitHub

About this component

An exam form with timing marks and a cut corner, the sort you fill in with a soft pencil. Tick it and a pencil scribbles the oval in, going over the lines a bit.

Dependencies
None
Difficulty
Intermediate
Triggers
click, state change, draw
Techniques
stroke dashoffset, keyframes, clip path, css transition, svg
Reduced motion
Respected
Theme
Light and dark (follows your page's color-scheme)

A native checkbox inside a <label>, so "6. I agree to the terms" is its name. The other questions and the marks are aria-hidden, and keyboard focus outlines the oval. Under prefers-reduced-motion the oval fills at once and the pencil never shows.

More checkboxes

See all
Buckle
Cat in a box
Checkmate
Cheque