{
 "$schema": "../../schema.json",
 "schema": "fundus/1",
 "id": "checkbox-radio-animiert",
 "name": "Animierte Checkbox und Radio",
 "name_en": "Animated checkbox / radio",
 "kurz": "Native Checkboxen und Radios mit eigener Optik: Häkchen zeichnet sich, Maske skaliert oder Quadrat füllt sich, die Semantik bleibt.",
 "kategorie": "Mikroanimationen",
 "kategorie_slug": "mikroanimationen",
 "kategorie_nr": 9,
 "bau_chat": "C4",
 "prioritaet": "B",
 "trendstatus": "etabliert",
 "belegstatus": "belegt",
 "website_belege": 2,
 "stimmung": [
  "präzise"
 ],
 "stimmung_roh": [
  "funktional",
  "präzise"
 ],
 "technik": [
  "css",
  "svg"
 ],
 "technik_gruppen": [
  "css",
  "canvas"
 ],
 "leistungskosten": "niedrig",
 "steckbrief": {
  "beschreibung": "Native Checkboxen und Radios bekommen per appearance: none eine eigene Optik und behalten ihre Semantik. Beim Anwählen skaliert das Icon von 0 auf 1, zeichnet sich per stroke-dashoffset ein oder ein Füllquadrat wächst aus der Mitte. Der Radio-Punkt wächst mit. Der Zustand indeterminate zeigt einen Strich. Das Icon liegt als SVG-Maske auf currentColor und funktioniert so auch im forced-colors-Modus.",
  "warum_es_wirkt": "Die kleine Bewegung bestätigt die Eingabe sofort und lässt das Formular sorgfältig wirken. Weil das native Input bleibt, gehen Tastaturbedienung, Formularversand und Screenreader-Ansage nicht verloren.",
  "wann_einsetzen": "Formulare, Filter, Einwilligungen, Auswahllisten (z. B. Bilder für Abzüge) und To-do-Listen.",
  "wann_nicht": "Keine großen Effekte wie Konfetti bei Pflicht-Checkboxen (AGB, Datenschutz). Keine langen Federn in dichten Listen, in denen viele Kästen schnell nacheinander angeklickt werden.",
  "varianten": [
   "Maske skaliert: Icon als SVG-Maske, scale 0 → 1 in 150 ms ease (Chrome-Leitfaden)",
   "Häkchen zeichnet sich: stroke-dashoffset in 250–350 ms",
   "Füllquadrat wächst: scale 0 → 1 in 300 ms cubic-bezier(0.19, 1, 0.22, 1), Rahmenfarbe 300 ms ease-out (Fantasy)",
   "Label wird bei To-dos durchgestrichen",
   "Komponenten aus Base UI oder Radix mit Motion"
  ],
  "umsetzung": ".cb { appearance: none; display: inline-grid; place-content: center; inline-size: 1.25em; aspect-ratio: 1; border: 2px solid currentColor } .cb::before { content: ''; inline-size: .7em; aspect-ratio: 1; background: currentColor; mask: var(--icon) no-repeat center / contain; scale: 0 } @media (prefers-reduced-motion: no-preference) { .cb::before { transition: scale 150ms ease } } .cb:checked { --icon: var(--icon-check) } .cb:indeterminate { --icon: var(--icon-dash) } .cb:is(:checked, :indeterminate)::before { scale: 1 } .cb:focus-visible { outline: 2px solid currentColor; outline-offset: 2px }. Für die Zeichnen-Variante liegt ein SVG-Pfad mit pathLength=\"1\" über dem Input; :has(.cb:checked) setzt stroke-dashoffset auf 0. indeterminate setzt man per JS (input.indeterminate = true).",
  "mobil": "Das Label ist die Trefferfläche, mindestens 44 px hoch. Auf Touch den Kasten etwas größer zeichnen (z. B. 22 px).",
  "reduzierte_bewegung": "Das Icon erscheint ohne Skalierung und ohne Zeichnen, höchstens mit kurzem Einblenden (100 ms).",
  "barrierefreiheit": "Das native Input bleibt im DOM und fokussierbar. indeterminate per JS-Property setzen. Im forced-colors-Modus Icon und Rahmen in CanvasText zeichnen. Den Fokusring nie entfernen."
 },
 "messwerte": {
  "easing": "ease; cubic-bezier(.19,1,.22,1) (Fantasy); linear()-Bounce (Fiddle)",
  "dauer_ms": "150; 300 (Fantasy); 900–1050 (Fiddle)",
  "stagger_ms": "",
  "distanz": "scale 0 → 1 (Fantasy); scaleX 1.45 / scaleY 1.55 (Fiddle)",
  "sonstiges": "",
  "herkunft": "beobachtet",
  "quelle": "https://developer.chrome.com/docs/modern-web-guidance/guides/ui-components/checkbox; https://fantasy.co/ ; https://fiddle.digital/"
 },
 "kurve": {
  "bezier": [
   0.25,
   0.1,
   0.25,
   1
  ],
  "css": "cubic-bezier(0.25, 0.1, 0.25, 1)",
  "name": "ease",
  "dauer_ms": 150
 },
 "gesehen_bei": [
  {
   "seite": "Chrome Developers: Checkbox",
   "url": "https://developer.chrome.com/docs/modern-web-guidance/guides/ui-components/checkbox",
   "auszeichnung": "",
   "beobachtung": "appearance:none, ::before mit SVG-Maske, scale 0 → 1, transition scale 150ms ease nur bei no-preference, forced-colors CanvasText, Label-Hitbox ≥ 44 px.",
   "art": "artikel"
  },
  {
   "seite": "Motion Examples: Base UI Checkbox",
   "url": "https://motion.dev/examples/react-base-checkbox",
   "auszeichnung": "",
   "beobachtung": "Animierte Checkbox.",
   "art": "demo"
  },
  {
   "seite": "Fantasy",
   "url": "https://fantasy.co/",
   "auszeichnung": "",
   "beobachtung": "Live geprüft (_nuxt/Dynamic.BXrJvJUw.css): .checkbox__indicator 2.7rem Rahmen, ::before-Füllquadrat 1.6rem scale(0) → scale(1) mit transition transform .3s cubic-bezier(.19,1,.22,1); Rahmenfarbe .3s ease-out.",
   "art": "website"
  },
  {
   "seite": "Fiddle.Digital",
   "url": "https://fiddle.digital/",
   "auszeichnung": "",
   "beobachtung": "Live geprüft (Inline-CSS): Toggle .checkbox-wrap .checkbox-toggle span – bei Hover/Check scale 1 1.55 + scaleX(1.45) mit transform 1.05s und scale .9s var(--f-2-bounce) = linear(0,1.28 7%,1.42 10%,1.18 14%,.78 22%,.72 28%,…,1), translate .6s cubic-bezier(0,.81,.35,1).",
   "art": "website"
  }
 ],
 "tutorials": [
  {
   "titel": "Chrome: Checkbox",
   "url": "https://developer.chrome.com/docs/modern-web-guidance/guides/ui-components/checkbox"
  }
 ],
 "kombinierbar_mit": [
  "token-federnd-verspielt",
  "validierung-nach-interaktion"
 ],
 "urls": {
  "detail": "https://cecodesign.de/lab/fundus/mikroanimationen/checkbox-radio-animiert/",
  "meta": "https://cecodesign.de/lab/fundus/mikroanimationen/checkbox-radio-animiert/meta.json",
  "demo": "https://cecodesign.de/lab/fundus/mikroanimationen/checkbox-radio-animiert/demo"
 },
 "herkunft": {
  "quelldateien": [
   "hover.json",
   "r3-belege-b.json"
  ],
  "pruefnotizen": [
   "r3-belege-b.json (Runde 3, 31.10.2026): Zwei Produktionsbelege; Fiddle zeigt zusätzlich CSS linear() als Feder-Easing."
  ]
 },
 "bau": {
  "status": "fertig",
  "kurz": "Native Checkboxen und Radios mit eigener Optik: Häkchen zeichnet sich, Maske skaliert oder Quadrat füllt sich, die Semantik bleibt.",
  "demo": true,
  "abhaengigkeiten": [],
  "werte": {
   "maske": {
    "eigenschaft": "scale 0 → 1 (::before mit SVG-Maske, Farbe currentColor)",
    "dauer_ms": 150,
    "easing": "cubic-bezier(0.25, 0.1, 0.25, 1)",
    "kasten": "background-color 150 ms, gleiche Kurve",
    "radio": "Punkt 8 px scale 0 → 1, 150 ms, gleiche Kurve",
    "herkunft": "Chrome-Leitfaden"
   },
   "zeichnen": {
    "eigenschaft": "stroke-dashoffset 1.05 → 0 (pathLength 1, stroke-dasharray 1 1.1, Strich 1.9 px, round caps)",
    "dauer_ms": 300,
    "easing": "cubic-bezier(0.19, 1, 0.22, 1)",
    "radio": "Punkt 8 px scale 0 → 1, 300 ms, gleiche Kurve",
    "herkunft": "Dauer im Steckbrief-Bereich 250–350 ms, Kurve von Fantasy"
   },
   "fuellen": {
    "eigenschaft": "Quadrat 10 px in 18 px Kasten (Fantasy 1,6 rem in 2,7 rem) scale 0 → 1",
    "dauer_ms": 300,
    "easing": "cubic-bezier(0.19, 1, 0.22, 1)",
    "rahmen": "border-color 300 ms ease-out cubic-bezier(0, 0, 0.58, 1)",
    "radio": "Punkt 10 px scale 0 → 1, 300 ms",
    "herkunft": "fantasy.co, live geprüft"
   },
   "kasten_px": "18 (Touch 22), Rahmen 1.5 px, Radius 2 px",
   "indeterminate": "Strich-Icon bzw. Strich-Pfad, gesetzt per JS-Property input.indeterminate",
   "auswahlrahmen": "outline 1 px, offset 4 px, outline-color über die Variantendauer mit cubic-bezier(0.455, 0.03, 0.515, 0.955)",
   "hover": "Rahmenfarbe rgba(20,20,20,.42) → #141414",
   "fokus": "outline 2 px solid currentColor, offset 2 px",
   "trefferflaeche_px": "Zeilen mindestens 44 (Touch 52), Bildunterschrift 44, ganzes Bild ist Label",
   "reduziert": "keine Skalierung, kein Zeichnen: opacity 0 → 1 in 100 ms linear"
  },
  "ausloeser": "klick",
  "touch": "Kästen wachsen auf 22 px, Zeilen auf 52 px; das ganze Bild und die ganze Zeile sind Label und damit Tippfläche. Kein Hover-Zustand nötig.",
  "reduziert": "Keine Skalierung und kein Zeichnen. Icon, Füllquadrat und Radio-Punkt blenden in 100 ms linear ein, alle anderen Übergänge entfallen.",
  "korrekturen": {
   "beschreibung": "Native Checkboxen und Radios bekommen per appearance: none eine eigene Optik und behalten ihre Semantik. Beim Anwählen skaliert das Icon von 0 auf 1, zeichnet sich per stroke-dashoffset ein oder ein Füllquadrat wächst aus der Mitte. Der Radio-Punkt wächst mit. Der Zustand indeterminate zeigt einen Strich. Das Icon liegt als SVG-Maske auf currentColor und funktioniert so auch im forced-colors-Modus.",
   "warum_es_wirkt": "Die kleine Bewegung bestätigt die Eingabe sofort und lässt das Formular sorgfältig wirken. Weil das native Input bleibt, gehen Tastaturbedienung, Formularversand und Screenreader-Ansage nicht verloren.",
   "wann_einsetzen": "Formulare, Filter, Einwilligungen, Auswahllisten (z. B. Bilder für Abzüge) und To-do-Listen.",
   "wann_nicht": "Keine großen Effekte wie Konfetti bei Pflicht-Checkboxen (AGB, Datenschutz). Keine langen Federn in dichten Listen, in denen viele Kästen schnell nacheinander angeklickt werden.",
   "varianten": [
    "Maske skaliert: Icon als SVG-Maske, scale 0 → 1 in 150 ms ease (Chrome-Leitfaden)",
    "Häkchen zeichnet sich: stroke-dashoffset in 250–350 ms",
    "Füllquadrat wächst: scale 0 → 1 in 300 ms cubic-bezier(0.19, 1, 0.22, 1), Rahmenfarbe 300 ms ease-out (Fantasy)",
    "Label wird bei To-dos durchgestrichen",
    "Komponenten aus Base UI oder Radix mit Motion"
   ],
   "umsetzung": ".cb { appearance: none; display: inline-grid; place-content: center; inline-size: 1.25em; aspect-ratio: 1; border: 2px solid currentColor } .cb::before { content: ''; inline-size: .7em; aspect-ratio: 1; background: currentColor; mask: var(--icon) no-repeat center / contain; scale: 0 } @media (prefers-reduced-motion: no-preference) { .cb::before { transition: scale 150ms ease } } .cb:checked { --icon: var(--icon-check) } .cb:indeterminate { --icon: var(--icon-dash) } .cb:is(:checked, :indeterminate)::before { scale: 1 } .cb:focus-visible { outline: 2px solid currentColor; outline-offset: 2px }. Für die Zeichnen-Variante liegt ein SVG-Pfad mit pathLength=\"1\" über dem Input; :has(.cb:checked) setzt stroke-dashoffset auf 0. indeterminate setzt man per JS (input.indeterminate = true).",
   "mobil": "Das Label ist die Trefferfläche, mindestens 44 px hoch. Auf Touch den Kasten etwas größer zeichnen (z. B. 22 px).",
   "reduzierte_bewegung": "Das Icon erscheint ohne Skalierung und ohne Zeichnen, höchstens mit kurzem Einblenden (100 ms).",
   "barrierefreiheit": "Das native Input bleibt im DOM und fokussierbar. indeterminate per JS-Property setzen. Im forced-colors-Modus Icon und Rahmen in CanvasText zeichnen. Den Fokusring nie entfernen."
  },
  "geprueft": "01.11.2026",
  "notiz": "Welt aus dem Rezept „Bild zuerst“ (Fotografie): Kundengalerie der erfundenen Grazer Fotografin Lena Kalchberg, Serie „Mur, früh“. Ein Büro wählt Bilder vom Kontaktbogen und bestellt Abzüge. Checkboxen am Bild (das ganze Bild ist Label), eine Sammel-Checkbox mit indeterminate, Radios für Papier und Format, Checkboxen für Extras, Summe rechnet mit. Drei Varianten per Umschalter: Maske (Chrome, 150 ms ease), Zeichnen (stroke-dashoffset, 300 ms), Füllen (Fantasy, 300 ms). Die Fotos sind eigene SVG-Zeichnungen; das Werk wird nach Rezept nie getönt, die Auswahl zeigt ein Rahmen außerhalb des Bildes. Die Fiddle-Feder (linear(), 900–1050 ms) ist nicht gebaut, weil der gemessene Verlauf nur teilweise vorliegt und sie zur ruhigen Fotografen-Welt nicht passt. Das Durchstreichen von To-dos ist nicht gezeigt. vorfuehren() klickt mit einem gezeichneten Zeiger per input.click() durch Bilder, Sammel-Checkbox, Papier, Format und Extras und wechselt pro Runde die Variante. AD-Review 01.11.2026: Note 4 Zugang 02.11.2026: --leise #8a8a8a → #666666 (5,2:1); Kontrastmodus-Rahmen für Knöpfe."
 }
}
