{
  "$schema": "./components.schema.json",
  "name": "Greda Design System component registry",
  "version": "0.7.0",
  "generatedFrom": "index.html",
  "documentation": "https://www.greda.design/",
  "stylesheet": "https://www.greda.design/v0.7.0/greda.css",
  "validator": "node scripts/validate.mjs your-file.html",
  "components": [
    {
      "id": "button",
      "name": "Button",
      "category": "Actions",
      "description": "Primary, secondary, quiet and disabled.",
      "docs": "https://www.greda.design/#button",
      "search": [
        "button",
        "Button",
        "Primary, secondary, quiet and disabled."
      ],
      "classes": [
        "gr-button",
        "gr-button--primary",
        "gr-button--quiet",
        "gr-button--secondary",
        "gr-cluster"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "primary",
        "secondary",
        "quiet",
        "danger",
        "disabled"
      ],
      "requires": [],
      "accessibility": [
        "Use a native button and declare type.",
        "Keep visible focus and disabled semantics."
      ],
      "implementation": {
        "html": "<div class=\"gr-cluster\">\n  <button class=\"gr-button gr-button--primary\" type=\"button\">Save</button>\n  <button class=\"gr-button gr-button--secondary\" type=\"button\">Duplicate</button>\n  <button class=\"gr-button gr-button--quiet\" type=\"button\">Cancel</button>\n  <button class=\"gr-button gr-button--secondary\" type=\"button\" disabled>Unavailable</button>\n</div>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Button component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-cluster\">\n  <button class=\"gr-button gr-button--primary\" type=\"button\">Save</button>\n  <button class=\"gr-button gr-button--secondary\" type=\"button\">Duplicate</button>\n  <button class=\"gr-button gr-button--quiet\" type=\"button\">Cancel</button>\n  <button class=\"gr-button gr-button--secondary\" type=\"button\" disabled>Unavailable</button>\n</div>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use a native button and declare type.\n- Keep visible focus and disabled semantics.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "icon-button",
      "name": "Icon Button",
      "category": "Actions",
      "description": "A square action with an accessible name and one exact Grid22 icon.",
      "docs": "https://www.greda.design/#icon-button",
      "search": [
        "icon-button",
        "Icon Button",
        "A square action with an accessible name and one exact Grid22 icon.",
        "icon action",
        "square button",
        "toolbar button"
      ],
      "classes": [
        "gr-button",
        "gr-button--danger",
        "gr-button--icon",
        "gr-button--quiet",
        "gr-button--secondary",
        "gr-cluster",
        "gr-icon"
      ],
      "tokens": [],
      "icons": [
        "search",
        "more-horizontal",
        "trash",
        "close"
      ],
      "states": [
        "secondary",
        "quiet",
        "danger",
        "disabled"
      ],
      "requires": [
        "Grid22"
      ],
      "accessibility": [
        "Give every icon-only button an accessible name.",
        "Hide the decorative Grid22 SVG with aria-hidden.",
        "Keep a 44px touch target and a 32px precise-pointer target."
      ],
      "implementation": {
        "html": "<div class=\"gr-cluster\">\n  <button class=\"gr-button gr-button--secondary gr-button--icon\" type=\"button\" aria-label=\"Search\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <circle cx=\"9.82\" cy=\"9.82\" r=\"6.67\"></circle>\n      <path d=\"M15.05 15.05L20.85 20.85\"></path>\n    </svg>\n  </button>\n  <button class=\"gr-button gr-button--quiet gr-button--icon\" type=\"button\" aria-label=\"More actions\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <circle cx=\"5\" cy=\"12\" r=\"1\"></circle>\n      <circle cx=\"12\" cy=\"12\" r=\"1\"></circle>\n      <circle cx=\"19\" cy=\"12\" r=\"1\"></circle>\n    </svg>\n  </button>\n  <button class=\"gr-button gr-button--danger gr-button--icon\" type=\"button\" aria-label=\"Delete project\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M4 7h16\"></path>\n      <path d=\"M10 11v6\"></path>\n      <path d=\"M14 11v6\"></path>\n      <path d=\"M6 7l0.92 12a2 2 0 0 0 2 1.85h6.16a2 2 0 0 0 2-1.85L18 7\"></path>\n      <path d=\"M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3\"></path>\n    </svg>\n  </button>\n  <button class=\"gr-button gr-button--secondary gr-button--icon\" type=\"button\" aria-label=\"Unavailable action\" disabled>\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M18 6L6 18\"></path>\n      <path d=\"M6 6L18 18\"></path>\n    </svg>\n  </button>\n</div>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Icon Button component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-cluster\">\n  <button class=\"gr-button gr-button--secondary gr-button--icon\" type=\"button\" aria-label=\"Search\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <circle cx=\"9.82\" cy=\"9.82\" r=\"6.67\"></circle>\n      <path d=\"M15.05 15.05L20.85 20.85\"></path>\n    </svg>\n  </button>\n  <button class=\"gr-button gr-button--quiet gr-button--icon\" type=\"button\" aria-label=\"More actions\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <circle cx=\"5\" cy=\"12\" r=\"1\"></circle>\n      <circle cx=\"12\" cy=\"12\" r=\"1\"></circle>\n      <circle cx=\"19\" cy=\"12\" r=\"1\"></circle>\n    </svg>\n  </button>\n  <button class=\"gr-button gr-button--danger gr-button--icon\" type=\"button\" aria-label=\"Delete project\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M4 7h16\"></path>\n      <path d=\"M10 11v6\"></path>\n      <path d=\"M14 11v6\"></path>\n      <path d=\"M6 7l0.92 12a2 2 0 0 0 2 1.85h6.16a2 2 0 0 0 2-1.85L18 7\"></path>\n      <path d=\"M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3\"></path>\n    </svg>\n  </button>\n  <button class=\"gr-button gr-button--secondary gr-button--icon\" type=\"button\" aria-label=\"Unavailable action\" disabled>\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M18 6L6 18\"></path>\n      <path d=\"M6 6L18 18\"></path>\n    </svg>\n  </button>\n</div>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Give every icon-only button an accessible name.\n- Hide the decorative Grid22 SVG with aria-hidden.\n- Keep a 44px touch target and a 32px precise-pointer target.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "tooltip",
      "name": "Tooltip",
      "category": "Feedback",
      "description": "Short supporting text on hover and focus; never the only accessible name.",
      "docs": "https://www.greda.design/#tooltip",
      "search": [
        "tooltip",
        "Tooltip",
        "Short supporting text on hover and focus; never the only accessible name.",
        "hint",
        "hover label",
        "description"
      ],
      "classes": [
        "gr-button",
        "gr-button--icon",
        "gr-button--secondary",
        "gr-icon",
        "gr-tooltip",
        "gr-tooltip-wrap"
      ],
      "tokens": [],
      "icons": [
        "edit"
      ],
      "states": [
        "hidden",
        "hover",
        "focus",
        "dismissed"
      ],
      "requires": [
        "Grid22",
        "Escape handler"
      ],
      "accessibility": [
        "Keep the trigger's accessible name; the tooltip only describes it.",
        "Connect the tooltip with aria-describedby.",
        "Show it on hover and focus, keep it hoverable, and dismiss it on Escape."
      ],
      "implementation": {
        "html": "<span class=\"gr-tooltip-wrap\">\n  <button class=\"gr-button gr-button--secondary gr-button--icon\" type=\"button\" aria-label=\"Edit project\" aria-describedby=\"tooltip-edit\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M12 20h9\"></path>\n      <path d=\"M16.5 4.5a2.12 2.12 0 0 1 3 3L7 20l-4 1 1-4z\"></path>\n    </svg>\n  </button>\n  <span class=\"gr-tooltip\" id=\"tooltip-edit\" role=\"tooltip\">Edit project</span>\n</span>",
        "javascript": "document.querySelectorAll(\".gr-tooltip-wrap\").forEach((wrapper) => {\n  const reset = (event) => {\n    if (!wrapper.contains(event.relatedTarget)) {\n      wrapper.removeAttribute(\"data-gr-tooltip-dismissed\");\n    }\n  };\n\n  wrapper.addEventListener(\"keydown\", (event) => {\n    if (event.key === \"Escape\") {\n      wrapper.setAttribute(\"data-gr-tooltip-dismissed\", \"true\");\n    }\n  });\n  wrapper.addEventListener(\"pointerleave\", reset);\n  wrapper.addEventListener(\"focusout\", reset);\n});",
        "aiPrompt": "Implement the Tooltip component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<span class=\"gr-tooltip-wrap\">\n  <button class=\"gr-button gr-button--secondary gr-button--icon\" type=\"button\" aria-label=\"Edit project\" aria-describedby=\"tooltip-edit\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M12 20h9\"></path>\n      <path d=\"M16.5 4.5a2.12 2.12 0 0 1 3 3L7 20l-4 1 1-4z\"></path>\n    </svg>\n  </button>\n  <span class=\"gr-tooltip\" id=\"tooltip-edit\" role=\"tooltip\">Edit project</span>\n</span>\n```\n\nUse this JavaScript behavior:\n\n```js\ndocument.querySelectorAll(\".gr-tooltip-wrap\").forEach((wrapper) => {\n  const reset = (event) => {\n    if (!wrapper.contains(event.relatedTarget)) {\n      wrapper.removeAttribute(\"data-gr-tooltip-dismissed\");\n    }\n  };\n\n  wrapper.addEventListener(\"keydown\", (event) => {\n    if (event.key === \"Escape\") {\n      wrapper.setAttribute(\"data-gr-tooltip-dismissed\", \"true\");\n    }\n  });\n  wrapper.addEventListener(\"pointerleave\", reset);\n  wrapper.addEventListener(\"focusout\", reset);\n});\n```\n\nAccessibility requirements:\n- Keep the trigger's accessible name; the tooltip only describes it.\n- Connect the tooltip with aria-describedby.\n- Show it on hover and focus, keep it hoverable, and dismiss it on Escape.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "link",
      "name": "Link",
      "category": "Content",
      "description": "The underline is always there; colour is never animated.",
      "docs": "https://www.greda.design/#link",
      "search": [
        "link",
        "Link",
        "The underline is always there; colour is never animated."
      ],
      "classes": [
        "gr-link",
        "gr-link--accent",
        "gr-link--quiet"
      ],
      "tokens": [],
      "icons": [],
      "states": [],
      "requires": [],
      "accessibility": [
        "Preserve semantic HTML.",
        "Keep visible focus on interactive elements."
      ],
      "implementation": {
        "html": "<p>\n  A <a class=\"gr-link\" href=\"#components\">default link</a>,\n  a <a class=\"gr-link gr-link--quiet\" href=\"#foundations\">quiet</a> one\n  and an <a class=\"gr-link gr-link--accent\" href=\"#principles\">accent</a> one.\n</p>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Link component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<p>\n  A <a class=\"gr-link\" href=\"#components\">default link</a>,\n  a <a class=\"gr-link gr-link--quiet\" href=\"#foundations\">quiet</a> one\n  and an <a class=\"gr-link gr-link--accent\" href=\"#principles\">accent</a> one.\n</p>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Preserve semantic HTML.\n- Keep visible focus on interactive elements.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "field",
      "name": "Field",
      "category": "Forms",
      "description": "Label, help text and visible focus.",
      "docs": "https://www.greda.design/#field",
      "search": [
        "field",
        "Field",
        "Label, help text and visible focus.",
        "input",
        "text input",
        "form control"
      ],
      "classes": [
        "gr-field",
        "gr-field__control",
        "gr-field__label",
        "gr-field__message",
        "gr-icon"
      ],
      "tokens": [],
      "icons": [
        "search"
      ],
      "states": [
        "default",
        "focus",
        "help"
      ],
      "requires": [],
      "accessibility": [
        "Keep a visible label.",
        "Associate help and error text with the control."
      ],
      "implementation": {
        "html": "<label class=\"gr-field\">\n  <span class=\"gr-field__label\">Search components</span>\n  <span class=\"gr-field__control\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <circle cx=\"9.82\" cy=\"9.82\" r=\"6.67\"></circle>\n      <path d=\"M15.05 15.05L20.85 20.85\"></path>\n    </svg>\n    <input type=\"search\" placeholder=\"Button, field, card…\">\n  </span>\n  <span class=\"gr-field__message\">Search by name or category.</span>\n</label>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Field component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<label class=\"gr-field\">\n  <span class=\"gr-field__label\">Search components</span>\n  <span class=\"gr-field__control\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <circle cx=\"9.82\" cy=\"9.82\" r=\"6.67\"></circle>\n      <path d=\"M15.05 15.05L20.85 20.85\"></path>\n    </svg>\n    <input type=\"search\" placeholder=\"Button, field, card…\">\n  </span>\n  <span class=\"gr-field__message\">Search by name or category.</span>\n</label>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Keep a visible label.\n- Associate help and error text with the control.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "textarea",
      "name": "Textarea",
      "category": "Forms",
      "description": "The same control, aligned to the top and resizable.",
      "docs": "https://www.greda.design/#textarea",
      "search": [
        "textarea",
        "Textarea",
        "The same control, aligned to the top and resizable.",
        "multiline",
        "long text"
      ],
      "classes": [
        "gr-field",
        "gr-field__control",
        "gr-field__control--multiline",
        "gr-field__label"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "default",
        "focus",
        "resizable"
      ],
      "requires": [],
      "accessibility": [
        "Keep a visible label.",
        "Do not prevent native resizing."
      ],
      "implementation": {
        "html": "<label class=\"gr-field\">\n  <span class=\"gr-field__label\">Decision notes</span>\n  <span class=\"gr-field__control gr-field__control--multiline\">\n    <textarea placeholder=\"Why it was taken, and what was rejected…\"></textarea>\n  </span>\n</label>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Textarea component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<label class=\"gr-field\">\n  <span class=\"gr-field__label\">Decision notes</span>\n  <span class=\"gr-field__control gr-field__control--multiline\">\n    <textarea placeholder=\"Why it was taken, and what was rejected…\"></textarea>\n  </span>\n</label>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Keep a visible label.\n- Do not prevent native resizing.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "select",
      "name": "Select",
      "category": "Forms",
      "description": "Native control; it only borrows the box.",
      "docs": "https://www.greda.design/#select",
      "search": [
        "select",
        "Select",
        "Native control; it only borrows the box.",
        "dropdown",
        "picker",
        "native select"
      ],
      "classes": [
        "gr-field",
        "gr-field__control",
        "gr-field__control--select",
        "gr-field__label",
        "gr-icon"
      ],
      "tokens": [],
      "icons": [
        "chevron-down"
      ],
      "states": [
        "default",
        "open",
        "selected"
      ],
      "requires": [],
      "accessibility": [
        "Use the native select so keyboard, forms, and type-ahead remain intact."
      ],
      "implementation": {
        "html": "<label class=\"gr-field\">\n  <span class=\"gr-field__label\">Density</span>\n  <span class=\"gr-field__control gr-field__control--select\">\n    <select>\n      <option>Compact — 32px</option>\n      <option>Touch — 44px</option>\n    </select>\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M6 9l6 6 6-6\"></path>\n    </svg>\n  </span>\n</label>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Select component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<label class=\"gr-field\">\n  <span class=\"gr-field__label\">Density</span>\n  <span class=\"gr-field__control gr-field__control--select\">\n    <select>\n      <option>Compact — 32px</option>\n      <option>Touch — 44px</option>\n    </select>\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M6 9l6 6 6-6\"></path>\n    </svg>\n  </span>\n</label>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use the native select so keyboard, forms, and type-ahead remain intact.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "cascader",
      "name": "Cascader",
      "category": "Forms",
      "description": "Select one value by moving through related levels.",
      "docs": "https://www.greda.design/#cascader",
      "search": [
        "cascader",
        "Cascader",
        "Select one value by moving through related levels.",
        "hierarchical select",
        "multi level picker",
        "path selector"
      ],
      "classes": [
        "gr-cascader",
        "gr-cascader__column",
        "gr-cascader__columns",
        "gr-cascader__option",
        "gr-cascader__panel",
        "gr-cascader__path",
        "gr-field",
        "gr-field__control",
        "gr-field__control--picker",
        "gr-field__label",
        "gr-icon",
        "gr-popover"
      ],
      "tokens": [],
      "icons": [
        "chevron-down",
        "chevron-right"
      ],
      "states": [
        "closed",
        "open",
        "parent selected",
        "value selected"
      ],
      "requires": [
        "Field",
        "Grid22",
        "Popover API",
        "Cascader keyboard script"
      ],
      "accessibility": [
        "Expose each level as a named listbox.",
        "Support Arrow Up/Down, Arrow Left/Right, Home, End, Enter, Space, and Escape.",
        "Keep the hidden form value and visible path synchronized."
      ],
      "implementation": {
        "html": "<div class=\"gr-cascader\" data-gr-cascader>\n  <label class=\"gr-field\" for=\"cascader-value\">\n    <span class=\"gr-field__label\">Team location</span>\n    <span class=\"gr-field__control gr-field__control--picker\">\n      <input id=\"cascader-value\" type=\"text\" value=\"Americas / Chile\" readonly role=\"combobox\" aria-haspopup=\"listbox\" aria-expanded=\"false\" aria-controls=\"cascader-options\" data-gr-cascader-trigger data-gr-cascader-value>\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M6 9l6 6 6-6\"></path>\n      </svg>\n    </span>\n  </label>\n  <input type=\"hidden\" name=\"team-location\" value=\"americas/chile\" data-gr-cascader-input>\n  <div class=\"gr-popover gr-cascader__panel\" id=\"cascader-options\" popover>\n    <p class=\"gr-cascader__path\" data-gr-cascader-path aria-live=\"polite\">Americas / Chile</p>\n    <div class=\"gr-cascader__columns\">\n      <div class=\"gr-cascader__column\" role=\"listbox\" aria-label=\"Region\" data-gr-cascader-column=\"root\">\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"true\" data-gr-cascader-option data-next=\"americas\" data-label=\"Americas\">\n          Americas\n          <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n            <path d=\"M9 18l6-6-6-6\"></path>\n          </svg>\n        </button>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-next=\"europe\" data-label=\"Europe\">\n          Europe\n          <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n            <path d=\"M9 18l6-6-6-6\"></path>\n          </svg>\n        </button>\n      </div>\n      <div class=\"gr-cascader__column\" role=\"listbox\" aria-label=\"Country in Americas\" data-gr-cascader-column=\"americas\" data-parent=\"root\">\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-value=\"americas/argentina\" data-label=\"Argentina\">Argentina</button>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"true\" data-gr-cascader-option data-value=\"americas/chile\" data-label=\"Chile\">Chile</button>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-value=\"americas/colombia\" data-label=\"Colombia\">Colombia</button>\n      </div>\n      <div class=\"gr-cascader__column\" role=\"listbox\" aria-label=\"Country in Europe\" data-gr-cascader-column=\"europe\" data-parent=\"root\" hidden>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-value=\"europe/portugal\" data-label=\"Portugal\">Portugal</button>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-value=\"europe/spain\" data-label=\"Spain\">Spain</button>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-value=\"europe/sweden\" data-label=\"Sweden\">Sweden</button>\n      </div>\n    </div>\n  </div>\n</div>",
        "javascript": "document.querySelectorAll(\"[data-gr-cascader]\").forEach((cascader) => {\n  const trigger = cascader.querySelector(\"[data-gr-cascader-trigger]\");\n  const panel = cascader.querySelector(\".gr-cascader__panel\");\n  const value = cascader.querySelector(\"[data-gr-cascader-value]\");\n  const path = cascader.querySelector(\"[data-gr-cascader-path]\");\n  const input = cascader.querySelector(\"[data-gr-cascader-input]\");\n  const columns = [...cascader.querySelectorAll(\"[data-gr-cascader-column]\")];\n  const root = columns.find((column) => column.dataset.grCascaderColumn === \"root\");\n  const optionsIn = (column) => [...column.querySelectorAll(\"[data-gr-cascader-option]\")];\n\n  function positionPanel() {\n    const control = trigger.closest(\".gr-field__control\") || trigger;\n    const rootStyles = getComputedStyle(document.documentElement);\n    const edge = parseFloat(rootStyles.getPropertyValue(\"--gr-space-4\")) || 16;\n    const gap = parseFloat(rootStyles.getPropertyValue(\"--gr-space-1\")) || 4;\n    const viewport = window.visualViewport;\n    const viewportLeft = viewport?.offsetLeft || 0;\n    const viewportTop = viewport?.offsetTop || 0;\n    const viewportWidth = viewport?.width || window.innerWidth;\n    const viewportHeight = viewport?.height || window.innerHeight;\n    const triggerRect = control.getBoundingClientRect();\n    const panelWidth = Math.min(triggerRect.width, viewportWidth - edge * 2);\n    panel.style.setProperty(\"--_gr-picker-width\", `${panelWidth}px`);\n    const panelRect = panel.getBoundingClientRect();\n    const left = Math.min(Math.max(viewportLeft + edge, triggerRect.left),\n      Math.max(viewportLeft + edge, viewportLeft + viewportWidth - panelRect.width - edge));\n    const below = triggerRect.bottom + gap;\n    const top = below + panelRect.height <= viewportTop + viewportHeight - edge\n      ? below : Math.max(viewportTop + edge, triggerRect.top - panelRect.height - gap);\n    panel.style.setProperty(\"--_gr-picker-left\", `${left}px`);\n    panel.style.setProperty(\"--_gr-picker-top\", `${top}px`);\n  }\n\n  function openColumn(name, focus = false) {\n    const next = columns.find((column) => column.dataset.grCascaderColumn === name);\n    if (!root || !next) return;\n    columns.forEach((column) => {\n      if (column !== root) column.hidden = column !== next;\n    });\n    optionsIn(root).forEach((option) => {\n      option.setAttribute(\"aria-selected\", String(option.dataset.next === name));\n    });\n    const parent = optionsIn(root).find((option) => option.dataset.next === name);\n    path.textContent = parent?.dataset.label || \"\";\n    if (focus) requestAnimationFrame(() => (optionsIn(next).find((option) =>\n      option.getAttribute(\"aria-selected\") === \"true\") || optionsIn(next)[0])?.focus());\n  }\n\n  function commit(option) {\n    const column = option.closest(\"[data-gr-cascader-column]\");\n    const parent = optionsIn(root).find((candidate) =>\n      candidate.dataset.next === column.dataset.grCascaderColumn);\n    optionsIn(column).forEach((candidate) => {\n      candidate.setAttribute(\"aria-selected\", String(candidate === option));\n    });\n    const label = [parent?.dataset.label, option.dataset.label].filter(Boolean).join(\" / \");\n    value.value = label;\n    path.textContent = label;\n    input.value = option.dataset.value || \"\";\n    input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n    panel.hidePopover();\n    trigger.focus();\n  }\n\n  panel.addEventListener(\"click\", (event) => {\n    const option = event.target.closest(\"[data-gr-cascader-option]\");\n    if (!option) return;\n    if (option.dataset.next) openColumn(option.dataset.next, true);\n    else commit(option);\n  });\n\n  panel.addEventListener(\"keydown\", (event) => {\n    const option = event.target.closest(\"[data-gr-cascader-option]\");\n    if (!option) return;\n    const column = option.closest(\"[data-gr-cascader-column]\");\n    const available = optionsIn(column);\n    const index = available.indexOf(option);\n    const positions = {\n      ArrowDown: (index + 1) % available.length,\n      ArrowUp: (index - 1 + available.length) % available.length,\n      Home: 0,\n      End: available.length - 1,\n    };\n    if (positions[event.key] !== undefined) {\n      event.preventDefault();\n      available[positions[event.key]].focus();\n    }\n    if (event.key === \"ArrowRight\" && option.dataset.next) {\n      event.preventDefault();\n      openColumn(option.dataset.next, true);\n    }\n    if (event.key === \"ArrowLeft\" && column !== root) {\n      event.preventDefault();\n      optionsIn(root).find((candidate) =>\n        candidate.dataset.next === column.dataset.grCascaderColumn)?.focus();\n    }\n    if ([\"Enter\", \" \"].includes(event.key)) {\n      event.preventDefault();\n      option.click();\n    }\n    if (event.key === \"Escape\") {\n      panel.hidePopover();\n      trigger.focus();\n    }\n  });\n\n  panel.addEventListener(\"toggle\", () => {\n    const open = panel.matches(\":popover-open\");\n    trigger.setAttribute(\"aria-expanded\", String(open));\n    if (open) requestAnimationFrame(() => {\n      positionPanel();\n      (optionsIn(root).find((option) => option.getAttribute(\"aria-selected\") === \"true\") ||\n      optionsIn(root)[0])?.focus();\n    });\n  });\n\n  trigger.addEventListener(\"keydown\", (event) => {\n    if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(event.key)) return;\n    event.preventDefault();\n    if (!panel.matches(\":popover-open\")) {\n      panel.showPopover();\n      positionPanel();\n    }\n  });\n\n  trigger.addEventListener(\"click\", () => {\n    if (panel.matches(\":popover-open\")) panel.hidePopover();\n    else {\n      panel.showPopover();\n      positionPanel();\n    }\n  });\n\n  const reposition = () => {\n    if (panel.matches(\":popover-open\")) positionPanel();\n  };\n  window.addEventListener(\"resize\", reposition);\n  window.addEventListener(\"scroll\", reposition, true);\n  window.visualViewport?.addEventListener(\"resize\", reposition);\n  window.visualViewport?.addEventListener(\"scroll\", reposition);\n});",
        "aiPrompt": "Implement the Cascader component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-cascader\" data-gr-cascader>\n  <label class=\"gr-field\" for=\"cascader-value\">\n    <span class=\"gr-field__label\">Team location</span>\n    <span class=\"gr-field__control gr-field__control--picker\">\n      <input id=\"cascader-value\" type=\"text\" value=\"Americas / Chile\" readonly role=\"combobox\" aria-haspopup=\"listbox\" aria-expanded=\"false\" aria-controls=\"cascader-options\" data-gr-cascader-trigger data-gr-cascader-value>\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M6 9l6 6 6-6\"></path>\n      </svg>\n    </span>\n  </label>\n  <input type=\"hidden\" name=\"team-location\" value=\"americas/chile\" data-gr-cascader-input>\n  <div class=\"gr-popover gr-cascader__panel\" id=\"cascader-options\" popover>\n    <p class=\"gr-cascader__path\" data-gr-cascader-path aria-live=\"polite\">Americas / Chile</p>\n    <div class=\"gr-cascader__columns\">\n      <div class=\"gr-cascader__column\" role=\"listbox\" aria-label=\"Region\" data-gr-cascader-column=\"root\">\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"true\" data-gr-cascader-option data-next=\"americas\" data-label=\"Americas\">\n          Americas\n          <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n            <path d=\"M9 18l6-6-6-6\"></path>\n          </svg>\n        </button>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-next=\"europe\" data-label=\"Europe\">\n          Europe\n          <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n            <path d=\"M9 18l6-6-6-6\"></path>\n          </svg>\n        </button>\n      </div>\n      <div class=\"gr-cascader__column\" role=\"listbox\" aria-label=\"Country in Americas\" data-gr-cascader-column=\"americas\" data-parent=\"root\">\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-value=\"americas/argentina\" data-label=\"Argentina\">Argentina</button>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"true\" data-gr-cascader-option data-value=\"americas/chile\" data-label=\"Chile\">Chile</button>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-value=\"americas/colombia\" data-label=\"Colombia\">Colombia</button>\n      </div>\n      <div class=\"gr-cascader__column\" role=\"listbox\" aria-label=\"Country in Europe\" data-gr-cascader-column=\"europe\" data-parent=\"root\" hidden>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-value=\"europe/portugal\" data-label=\"Portugal\">Portugal</button>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-value=\"europe/spain\" data-label=\"Spain\">Spain</button>\n        <button class=\"gr-cascader__option\" type=\"button\" role=\"option\" aria-selected=\"false\" data-gr-cascader-option data-value=\"europe/sweden\" data-label=\"Sweden\">Sweden</button>\n      </div>\n    </div>\n  </div>\n</div>\n```\n\nUse this JavaScript behavior:\n\n```js\ndocument.querySelectorAll(\"[data-gr-cascader]\").forEach((cascader) => {\n  const trigger = cascader.querySelector(\"[data-gr-cascader-trigger]\");\n  const panel = cascader.querySelector(\".gr-cascader__panel\");\n  const value = cascader.querySelector(\"[data-gr-cascader-value]\");\n  const path = cascader.querySelector(\"[data-gr-cascader-path]\");\n  const input = cascader.querySelector(\"[data-gr-cascader-input]\");\n  const columns = [...cascader.querySelectorAll(\"[data-gr-cascader-column]\")];\n  const root = columns.find((column) => column.dataset.grCascaderColumn === \"root\");\n  const optionsIn = (column) => [...column.querySelectorAll(\"[data-gr-cascader-option]\")];\n\n  function positionPanel() {\n    const control = trigger.closest(\".gr-field__control\") || trigger;\n    const rootStyles = getComputedStyle(document.documentElement);\n    const edge = parseFloat(rootStyles.getPropertyValue(\"--gr-space-4\")) || 16;\n    const gap = parseFloat(rootStyles.getPropertyValue(\"--gr-space-1\")) || 4;\n    const viewport = window.visualViewport;\n    const viewportLeft = viewport?.offsetLeft || 0;\n    const viewportTop = viewport?.offsetTop || 0;\n    const viewportWidth = viewport?.width || window.innerWidth;\n    const viewportHeight = viewport?.height || window.innerHeight;\n    const triggerRect = control.getBoundingClientRect();\n    const panelWidth = Math.min(triggerRect.width, viewportWidth - edge * 2);\n    panel.style.setProperty(\"--_gr-picker-width\", `${panelWidth}px`);\n    const panelRect = panel.getBoundingClientRect();\n    const left = Math.min(Math.max(viewportLeft + edge, triggerRect.left),\n      Math.max(viewportLeft + edge, viewportLeft + viewportWidth - panelRect.width - edge));\n    const below = triggerRect.bottom + gap;\n    const top = below + panelRect.height <= viewportTop + viewportHeight - edge\n      ? below : Math.max(viewportTop + edge, triggerRect.top - panelRect.height - gap);\n    panel.style.setProperty(\"--_gr-picker-left\", `${left}px`);\n    panel.style.setProperty(\"--_gr-picker-top\", `${top}px`);\n  }\n\n  function openColumn(name, focus = false) {\n    const next = columns.find((column) => column.dataset.grCascaderColumn === name);\n    if (!root || !next) return;\n    columns.forEach((column) => {\n      if (column !== root) column.hidden = column !== next;\n    });\n    optionsIn(root).forEach((option) => {\n      option.setAttribute(\"aria-selected\", String(option.dataset.next === name));\n    });\n    const parent = optionsIn(root).find((option) => option.dataset.next === name);\n    path.textContent = parent?.dataset.label || \"\";\n    if (focus) requestAnimationFrame(() => (optionsIn(next).find((option) =>\n      option.getAttribute(\"aria-selected\") === \"true\") || optionsIn(next)[0])?.focus());\n  }\n\n  function commit(option) {\n    const column = option.closest(\"[data-gr-cascader-column]\");\n    const parent = optionsIn(root).find((candidate) =>\n      candidate.dataset.next === column.dataset.grCascaderColumn);\n    optionsIn(column).forEach((candidate) => {\n      candidate.setAttribute(\"aria-selected\", String(candidate === option));\n    });\n    const label = [parent?.dataset.label, option.dataset.label].filter(Boolean).join(\" / \");\n    value.value = label;\n    path.textContent = label;\n    input.value = option.dataset.value || \"\";\n    input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n    panel.hidePopover();\n    trigger.focus();\n  }\n\n  panel.addEventListener(\"click\", (event) => {\n    const option = event.target.closest(\"[data-gr-cascader-option]\");\n    if (!option) return;\n    if (option.dataset.next) openColumn(option.dataset.next, true);\n    else commit(option);\n  });\n\n  panel.addEventListener(\"keydown\", (event) => {\n    const option = event.target.closest(\"[data-gr-cascader-option]\");\n    if (!option) return;\n    const column = option.closest(\"[data-gr-cascader-column]\");\n    const available = optionsIn(column);\n    const index = available.indexOf(option);\n    const positions = {\n      ArrowDown: (index + 1) % available.length,\n      ArrowUp: (index - 1 + available.length) % available.length,\n      Home: 0,\n      End: available.length - 1,\n    };\n    if (positions[event.key] !== undefined) {\n      event.preventDefault();\n      available[positions[event.key]].focus();\n    }\n    if (event.key === \"ArrowRight\" && option.dataset.next) {\n      event.preventDefault();\n      openColumn(option.dataset.next, true);\n    }\n    if (event.key === \"ArrowLeft\" && column !== root) {\n      event.preventDefault();\n      optionsIn(root).find((candidate) =>\n        candidate.dataset.next === column.dataset.grCascaderColumn)?.focus();\n    }\n    if ([\"Enter\", \" \"].includes(event.key)) {\n      event.preventDefault();\n      option.click();\n    }\n    if (event.key === \"Escape\") {\n      panel.hidePopover();\n      trigger.focus();\n    }\n  });\n\n  panel.addEventListener(\"toggle\", () => {\n    const open = panel.matches(\":popover-open\");\n    trigger.setAttribute(\"aria-expanded\", String(open));\n    if (open) requestAnimationFrame(() => {\n      positionPanel();\n      (optionsIn(root).find((option) => option.getAttribute(\"aria-selected\") === \"true\") ||\n      optionsIn(root)[0])?.focus();\n    });\n  });\n\n  trigger.addEventListener(\"keydown\", (event) => {\n    if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(event.key)) return;\n    event.preventDefault();\n    if (!panel.matches(\":popover-open\")) {\n      panel.showPopover();\n      positionPanel();\n    }\n  });\n\n  trigger.addEventListener(\"click\", () => {\n    if (panel.matches(\":popover-open\")) panel.hidePopover();\n    else {\n      panel.showPopover();\n      positionPanel();\n    }\n  });\n\n  const reposition = () => {\n    if (panel.matches(\":popover-open\")) positionPanel();\n  };\n  window.addEventListener(\"resize\", reposition);\n  window.addEventListener(\"scroll\", reposition, true);\n  window.visualViewport?.addEventListener(\"resize\", reposition);\n  window.visualViewport?.addEventListener(\"scroll\", reposition);\n});\n```\n\nAccessibility requirements:\n- Expose each level as a named listbox.\n- Support Arrow Up/Down, Arrow Left/Right, Home, End, Enter, Space, and Escape.\n- Keep the hidden form value and visible path synchronized.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "tree-select",
      "name": "Tree Select",
      "category": "Forms",
      "description": "Select several values without losing their hierarchy.",
      "docs": "https://www.greda.design/#tree-select",
      "search": [
        "tree-select",
        "Tree Select",
        "Select several values without losing their hierarchy.",
        "hierarchy picker",
        "nested multiselect",
        "tree view"
      ],
      "classes": [
        "gr-choice",
        "gr-choice__input",
        "gr-choice__label",
        "gr-field",
        "gr-field__control",
        "gr-field__control--picker",
        "gr-field__label",
        "gr-fieldset",
        "gr-icon",
        "gr-popover",
        "gr-tree-select",
        "gr-tree-select__branch",
        "gr-tree-select__branch-toggle",
        "gr-tree-select__chevron",
        "gr-tree-select__choice",
        "gr-tree-select__group",
        "gr-tree-select__panel",
        "gr-tree-select__tree",
        "gr-visually-hidden"
      ],
      "tokens": [],
      "icons": [
        "chevron-down",
        "chevron-right",
        "check"
      ],
      "states": [
        "closed",
        "open",
        "expanded",
        "collapsed",
        "checked",
        "unchecked"
      ],
      "requires": [
        "Field",
        "Fieldset",
        "Choice",
        "Grid22",
        "Popover API"
      ],
      "accessibility": [
        "Compose the existing Field, Fieldset, and Choice components.",
        "Use native checkboxes so Space, focus, and form semantics remain intact.",
        "Connect each disclosure button to its checkbox group with aria-controls and aria-expanded."
      ],
      "implementation": {
        "html": "<div class=\"gr-tree-select\" data-gr-tree-select>\n  <label class=\"gr-field\" for=\"tree-select-value\">\n    <span class=\"gr-field__label\">Documentation sections</span>\n    <span class=\"gr-field__control gr-field__control--picker\">\n      <input id=\"tree-select-value\" type=\"text\" value=\"Color, Typography\" readonly role=\"combobox\" aria-haspopup=\"dialog\" aria-expanded=\"false\" aria-controls=\"tree-select-options\" data-gr-tree-select-trigger data-gr-tree-select-value>\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M6 9l6 6 6-6\"></path>\n      </svg>\n    </span>\n  </label>\n  <input type=\"hidden\" name=\"documentation-sections\" value=\"color,typography\" data-gr-tree-select-input>\n  <div class=\"gr-popover gr-tree-select__panel\" id=\"tree-select-options\" popover>\n    <fieldset class=\"gr-fieldset gr-tree-select__tree\">\n      <legend class=\"gr-visually-hidden\">Documentation sections</legend>\n      <div class=\"gr-tree-select__branch\">\n        <button class=\"gr-tree-select__branch-toggle\" type=\"button\" aria-expanded=\"true\" aria-controls=\"tree-foundation-options\" data-gr-tree-branch>\n          <svg class=\"gr-icon gr-tree-select__chevron\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n            <path d=\"M9 18l6-6-6-6\"></path>\n          </svg>\n          <span>Foundation</span>\n        </button>\n        <div class=\"gr-tree-select__group\" id=\"tree-foundation-options\">\n          <label class=\"gr-choice gr-tree-select__choice\">\n            <input class=\"gr-choice__input\" type=\"checkbox\" value=\"color\" checked data-gr-tree-option data-label=\"Color\">\n            <span class=\"gr-choice__label\">Color</span>\n          </label>\n          <label class=\"gr-choice gr-tree-select__choice\">\n            <input class=\"gr-choice__input\" type=\"checkbox\" value=\"typography\" checked data-gr-tree-option data-label=\"Typography\">\n            <span class=\"gr-choice__label\">Typography</span>\n          </label>\n          <label class=\"gr-choice gr-tree-select__choice\">\n            <input class=\"gr-choice__input\" type=\"checkbox\" value=\"motion\" data-gr-tree-option data-label=\"Motion\">\n            <span class=\"gr-choice__label\">Motion</span>\n          </label>\n        </div>\n      </div>\n      <div class=\"gr-tree-select__branch\">\n        <button class=\"gr-tree-select__branch-toggle\" type=\"button\" aria-expanded=\"false\" aria-controls=\"tree-components-options\" data-gr-tree-branch>\n          <svg class=\"gr-icon gr-tree-select__chevron\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n            <path d=\"M9 18l6-6-6-6\"></path>\n          </svg>\n          <span>Components</span>\n        </button>\n        <div class=\"gr-tree-select__group\" id=\"tree-components-options\" hidden>\n          <label class=\"gr-choice gr-tree-select__choice\">\n            <input class=\"gr-choice__input\" type=\"checkbox\" value=\"forms\" data-gr-tree-option data-label=\"Forms\">\n            <span class=\"gr-choice__label\">Forms</span>\n          </label>\n          <label class=\"gr-choice gr-tree-select__choice\">\n            <input class=\"gr-choice__input\" type=\"checkbox\" value=\"navigation\" data-gr-tree-option data-label=\"Navigation\">\n            <span class=\"gr-choice__label\">Navigation</span>\n          </label>\n        </div>\n      </div>\n    </fieldset>\n  </div>\n</div>",
        "javascript": "document.querySelectorAll(\"[data-gr-tree-select]\").forEach((select) => {\n  const trigger = select.querySelector(\"[data-gr-tree-select-trigger]\");\n  const panel = select.querySelector(\".gr-tree-select__panel\");\n  const tree = select.querySelector(\".gr-tree-select__tree\");\n  const value = select.querySelector(\"[data-gr-tree-select-value]\");\n  const input = select.querySelector(\"[data-gr-tree-select-input]\");\n  const options = () => [...tree.querySelectorAll(\"[data-gr-tree-option]\")];\n\n  function positionPanel() {\n    const control = trigger.closest(\".gr-field__control\") || trigger;\n    const rootStyles = getComputedStyle(document.documentElement);\n    const edge = parseFloat(rootStyles.getPropertyValue(\"--gr-space-4\")) || 16;\n    const gap = parseFloat(rootStyles.getPropertyValue(\"--gr-space-1\")) || 4;\n    const viewport = window.visualViewport;\n    const viewportLeft = viewport?.offsetLeft || 0;\n    const viewportTop = viewport?.offsetTop || 0;\n    const viewportWidth = viewport?.width || window.innerWidth;\n    const viewportHeight = viewport?.height || window.innerHeight;\n    const triggerRect = control.getBoundingClientRect();\n    const panelWidth = Math.min(triggerRect.width, viewportWidth - edge * 2);\n    panel.style.setProperty(\"--_gr-picker-width\", `${panelWidth}px`);\n    const panelRect = panel.getBoundingClientRect();\n    const left = Math.min(Math.max(viewportLeft + edge, triggerRect.left),\n      Math.max(viewportLeft + edge, viewportLeft + viewportWidth - panelRect.width - edge));\n    const below = triggerRect.bottom + gap;\n    const top = below + panelRect.height <= viewportTop + viewportHeight - edge\n      ? below : Math.max(viewportTop + edge, triggerRect.top - panelRect.height - gap);\n    panel.style.setProperty(\"--_gr-picker-left\", `${left}px`);\n    panel.style.setProperty(\"--_gr-picker-top\", `${top}px`);\n  }\n\n  function updateValue() {\n    const selected = options().filter((option) => option.checked);\n    const labels = selected.map((item) => item.dataset.label);\n    value.value = labels.length <= 2 ? labels.join(\", \") || \"Select sections\"\n      : `${labels.length} selected`;\n    input.value = selected.map((item) => item.value).join(\",\");\n    input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n  }\n\n  tree.addEventListener(\"click\", (event) => {\n    const branch = event.target.closest(\"[data-gr-tree-branch]\");\n    if (!branch) return;\n    const group = tree.querySelector(`#${CSS.escape(branch.getAttribute(\"aria-controls\"))}`);\n    const expanded = branch.getAttribute(\"aria-expanded\") !== \"true\";\n    branch.setAttribute(\"aria-expanded\", String(expanded));\n    if (group) group.hidden = !expanded;\n  });\n\n  tree.addEventListener(\"change\", (event) => {\n    if (event.target.matches(\"[data-gr-tree-option]\")) updateValue();\n  });\n\n  panel.addEventListener(\"keydown\", (event) => {\n    if (event.key !== \"Escape\") return;\n    event.preventDefault();\n    panel.hidePopover();\n    trigger.focus();\n  });\n\n  panel.addEventListener(\"toggle\", () => {\n    const open = panel.matches(\":popover-open\");\n    trigger.setAttribute(\"aria-expanded\", String(open));\n    if (open) requestAnimationFrame(() => {\n      positionPanel();\n      const firstControl = options().find((option) => option.checked &&\n        !option.closest(\"[hidden]\")) || tree.querySelector(\"[data-gr-tree-branch]\");\n      firstControl?.focus();\n    });\n  });\n\n  trigger.addEventListener(\"keydown\", (event) => {\n    if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(event.key)) return;\n    event.preventDefault();\n    if (!panel.matches(\":popover-open\")) {\n      panel.showPopover();\n      positionPanel();\n    }\n  });\n\n  trigger.addEventListener(\"click\", () => {\n    if (panel.matches(\":popover-open\")) panel.hidePopover();\n    else {\n      panel.showPopover();\n      positionPanel();\n    }\n  });\n\n  const reposition = () => {\n    if (panel.matches(\":popover-open\")) positionPanel();\n  };\n  window.addEventListener(\"resize\", reposition);\n  window.addEventListener(\"scroll\", reposition, true);\n  window.visualViewport?.addEventListener(\"resize\", reposition);\n  window.visualViewport?.addEventListener(\"scroll\", reposition);\n\n  updateValue();\n});",
        "aiPrompt": "Implement the Tree Select component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-tree-select\" data-gr-tree-select>\n  <label class=\"gr-field\" for=\"tree-select-value\">\n    <span class=\"gr-field__label\">Documentation sections</span>\n    <span class=\"gr-field__control gr-field__control--picker\">\n      <input id=\"tree-select-value\" type=\"text\" value=\"Color, Typography\" readonly role=\"combobox\" aria-haspopup=\"dialog\" aria-expanded=\"false\" aria-controls=\"tree-select-options\" data-gr-tree-select-trigger data-gr-tree-select-value>\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M6 9l6 6 6-6\"></path>\n      </svg>\n    </span>\n  </label>\n  <input type=\"hidden\" name=\"documentation-sections\" value=\"color,typography\" data-gr-tree-select-input>\n  <div class=\"gr-popover gr-tree-select__panel\" id=\"tree-select-options\" popover>\n    <fieldset class=\"gr-fieldset gr-tree-select__tree\">\n      <legend class=\"gr-visually-hidden\">Documentation sections</legend>\n      <div class=\"gr-tree-select__branch\">\n        <button class=\"gr-tree-select__branch-toggle\" type=\"button\" aria-expanded=\"true\" aria-controls=\"tree-foundation-options\" data-gr-tree-branch>\n          <svg class=\"gr-icon gr-tree-select__chevron\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n            <path d=\"M9 18l6-6-6-6\"></path>\n          </svg>\n          <span>Foundation</span>\n        </button>\n        <div class=\"gr-tree-select__group\" id=\"tree-foundation-options\">\n          <label class=\"gr-choice gr-tree-select__choice\">\n            <input class=\"gr-choice__input\" type=\"checkbox\" value=\"color\" checked data-gr-tree-option data-label=\"Color\">\n            <span class=\"gr-choice__label\">Color</span>\n          </label>\n          <label class=\"gr-choice gr-tree-select__choice\">\n            <input class=\"gr-choice__input\" type=\"checkbox\" value=\"typography\" checked data-gr-tree-option data-label=\"Typography\">\n            <span class=\"gr-choice__label\">Typography</span>\n          </label>\n          <label class=\"gr-choice gr-tree-select__choice\">\n            <input class=\"gr-choice__input\" type=\"checkbox\" value=\"motion\" data-gr-tree-option data-label=\"Motion\">\n            <span class=\"gr-choice__label\">Motion</span>\n          </label>\n        </div>\n      </div>\n      <div class=\"gr-tree-select__branch\">\n        <button class=\"gr-tree-select__branch-toggle\" type=\"button\" aria-expanded=\"false\" aria-controls=\"tree-components-options\" data-gr-tree-branch>\n          <svg class=\"gr-icon gr-tree-select__chevron\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n            <path d=\"M9 18l6-6-6-6\"></path>\n          </svg>\n          <span>Components</span>\n        </button>\n        <div class=\"gr-tree-select__group\" id=\"tree-components-options\" hidden>\n          <label class=\"gr-choice gr-tree-select__choice\">\n            <input class=\"gr-choice__input\" type=\"checkbox\" value=\"forms\" data-gr-tree-option data-label=\"Forms\">\n            <span class=\"gr-choice__label\">Forms</span>\n          </label>\n          <label class=\"gr-choice gr-tree-select__choice\">\n            <input class=\"gr-choice__input\" type=\"checkbox\" value=\"navigation\" data-gr-tree-option data-label=\"Navigation\">\n            <span class=\"gr-choice__label\">Navigation</span>\n          </label>\n        </div>\n      </div>\n    </fieldset>\n  </div>\n</div>\n```\n\nUse this JavaScript behavior:\n\n```js\ndocument.querySelectorAll(\"[data-gr-tree-select]\").forEach((select) => {\n  const trigger = select.querySelector(\"[data-gr-tree-select-trigger]\");\n  const panel = select.querySelector(\".gr-tree-select__panel\");\n  const tree = select.querySelector(\".gr-tree-select__tree\");\n  const value = select.querySelector(\"[data-gr-tree-select-value]\");\n  const input = select.querySelector(\"[data-gr-tree-select-input]\");\n  const options = () => [...tree.querySelectorAll(\"[data-gr-tree-option]\")];\n\n  function positionPanel() {\n    const control = trigger.closest(\".gr-field__control\") || trigger;\n    const rootStyles = getComputedStyle(document.documentElement);\n    const edge = parseFloat(rootStyles.getPropertyValue(\"--gr-space-4\")) || 16;\n    const gap = parseFloat(rootStyles.getPropertyValue(\"--gr-space-1\")) || 4;\n    const viewport = window.visualViewport;\n    const viewportLeft = viewport?.offsetLeft || 0;\n    const viewportTop = viewport?.offsetTop || 0;\n    const viewportWidth = viewport?.width || window.innerWidth;\n    const viewportHeight = viewport?.height || window.innerHeight;\n    const triggerRect = control.getBoundingClientRect();\n    const panelWidth = Math.min(triggerRect.width, viewportWidth - edge * 2);\n    panel.style.setProperty(\"--_gr-picker-width\", `${panelWidth}px`);\n    const panelRect = panel.getBoundingClientRect();\n    const left = Math.min(Math.max(viewportLeft + edge, triggerRect.left),\n      Math.max(viewportLeft + edge, viewportLeft + viewportWidth - panelRect.width - edge));\n    const below = triggerRect.bottom + gap;\n    const top = below + panelRect.height <= viewportTop + viewportHeight - edge\n      ? below : Math.max(viewportTop + edge, triggerRect.top - panelRect.height - gap);\n    panel.style.setProperty(\"--_gr-picker-left\", `${left}px`);\n    panel.style.setProperty(\"--_gr-picker-top\", `${top}px`);\n  }\n\n  function updateValue() {\n    const selected = options().filter((option) => option.checked);\n    const labels = selected.map((item) => item.dataset.label);\n    value.value = labels.length <= 2 ? labels.join(\", \") || \"Select sections\"\n      : `${labels.length} selected`;\n    input.value = selected.map((item) => item.value).join(\",\");\n    input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n  }\n\n  tree.addEventListener(\"click\", (event) => {\n    const branch = event.target.closest(\"[data-gr-tree-branch]\");\n    if (!branch) return;\n    const group = tree.querySelector(`#${CSS.escape(branch.getAttribute(\"aria-controls\"))}`);\n    const expanded = branch.getAttribute(\"aria-expanded\") !== \"true\";\n    branch.setAttribute(\"aria-expanded\", String(expanded));\n    if (group) group.hidden = !expanded;\n  });\n\n  tree.addEventListener(\"change\", (event) => {\n    if (event.target.matches(\"[data-gr-tree-option]\")) updateValue();\n  });\n\n  panel.addEventListener(\"keydown\", (event) => {\n    if (event.key !== \"Escape\") return;\n    event.preventDefault();\n    panel.hidePopover();\n    trigger.focus();\n  });\n\n  panel.addEventListener(\"toggle\", () => {\n    const open = panel.matches(\":popover-open\");\n    trigger.setAttribute(\"aria-expanded\", String(open));\n    if (open) requestAnimationFrame(() => {\n      positionPanel();\n      const firstControl = options().find((option) => option.checked &&\n        !option.closest(\"[hidden]\")) || tree.querySelector(\"[data-gr-tree-branch]\");\n      firstControl?.focus();\n    });\n  });\n\n  trigger.addEventListener(\"keydown\", (event) => {\n    if (![\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(event.key)) return;\n    event.preventDefault();\n    if (!panel.matches(\":popover-open\")) {\n      panel.showPopover();\n      positionPanel();\n    }\n  });\n\n  trigger.addEventListener(\"click\", () => {\n    if (panel.matches(\":popover-open\")) panel.hidePopover();\n    else {\n      panel.showPopover();\n      positionPanel();\n    }\n  });\n\n  const reposition = () => {\n    if (panel.matches(\":popover-open\")) positionPanel();\n  };\n  window.addEventListener(\"resize\", reposition);\n  window.addEventListener(\"scroll\", reposition, true);\n  window.visualViewport?.addEventListener(\"resize\", reposition);\n  window.visualViewport?.addEventListener(\"scroll\", reposition);\n\n  updateValue();\n});\n```\n\nAccessibility requirements:\n- Compose the existing Field, Fieldset, and Choice components.\n- Use native checkboxes so Space, focus, and form semantics remain intact.\n- Connect each disclosure button to its checkbox group with aria-controls and aria-expanded.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "choice",
      "name": "Choice",
      "category": "Forms",
      "description": "Checkbox and radio share everything but the shape.",
      "docs": "https://www.greda.design/#choice",
      "search": [
        "choice",
        "Choice",
        "Checkbox and radio share everything but the shape.",
        "checkbox",
        "radio"
      ],
      "classes": [
        "gr-choice",
        "gr-choice__input",
        "gr-choice__label",
        "gr-fieldset",
        "gr-fieldset__legend"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "checked",
        "unchecked",
        "radio"
      ],
      "requires": [],
      "accessibility": [
        "Use native checkbox and radio inputs inside a fieldset when grouped."
      ],
      "implementation": {
        "html": "<fieldset class=\"gr-fieldset\">\n  <legend class=\"gr-fieldset__legend\">What gets validated</legend>\n  <label class=\"gr-choice\">\n    <input class=\"gr-choice__input\" type=\"checkbox\" checked>\n    <span class=\"gr-choice__label\">Local references</span>\n  </label>\n  <label class=\"gr-choice\">\n    <input class=\"gr-choice__input\" type=\"checkbox\">\n    <span class=\"gr-choice__label\">Colours outside the tokens</span>\n  </label>\n  <label class=\"gr-choice\">\n    <input class=\"gr-choice__input\" type=\"radio\" name=\"density\" checked>\n    <span class=\"gr-choice__label\">This page only</span>\n  </label>\n  <label class=\"gr-choice\">\n    <input class=\"gr-choice__input\" type=\"radio\" name=\"density\">\n    <span class=\"gr-choice__label\">The whole system</span>\n  </label>\n</fieldset>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Choice component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<fieldset class=\"gr-fieldset\">\n  <legend class=\"gr-fieldset__legend\">What gets validated</legend>\n  <label class=\"gr-choice\">\n    <input class=\"gr-choice__input\" type=\"checkbox\" checked>\n    <span class=\"gr-choice__label\">Local references</span>\n  </label>\n  <label class=\"gr-choice\">\n    <input class=\"gr-choice__input\" type=\"checkbox\">\n    <span class=\"gr-choice__label\">Colours outside the tokens</span>\n  </label>\n  <label class=\"gr-choice\">\n    <input class=\"gr-choice__input\" type=\"radio\" name=\"density\" checked>\n    <span class=\"gr-choice__label\">This page only</span>\n  </label>\n  <label class=\"gr-choice\">\n    <input class=\"gr-choice__input\" type=\"radio\" name=\"density\">\n    <span class=\"gr-choice__label\">The whole system</span>\n  </label>\n</fieldset>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use native checkbox and radio inputs inside a fieldset when grouped.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "switch",
      "name": "Switch",
      "category": "Forms",
      "description": "A physical state: the thumb travels, the track does not animate.",
      "docs": "https://www.greda.design/#switch",
      "search": [
        "switch",
        "Switch",
        "A physical state: the thumb travels, the track does not animate.",
        "toggle"
      ],
      "classes": [
        "gr-switch",
        "gr-switch__input",
        "gr-switch__label"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "on",
        "off"
      ],
      "requires": [],
      "accessibility": [
        "Use a checkbox with role=switch and a visible label."
      ],
      "implementation": {
        "html": "<label class=\"gr-switch\">\n  <input class=\"gr-switch__input\" type=\"checkbox\" role=\"switch\" checked>\n  <span class=\"gr-switch__label\">Reduce motion</span>\n</label>\n<label class=\"gr-switch\">\n  <input class=\"gr-switch__input\" type=\"checkbox\" role=\"switch\">\n  <span class=\"gr-switch__label\">Force dark theme</span>\n</label>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Switch component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<label class=\"gr-switch\">\n  <input class=\"gr-switch__input\" type=\"checkbox\" role=\"switch\" checked>\n  <span class=\"gr-switch__label\">Reduce motion</span>\n</label>\n<label class=\"gr-switch\">\n  <input class=\"gr-switch__input\" type=\"checkbox\" role=\"switch\">\n  <span class=\"gr-switch__label\">Force dark theme</span>\n</label>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use a checkbox with role=switch and a visible label.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "states",
      "name": "States",
      "category": "Forms",
      "description": "Error, required, readonly and disabled.",
      "docs": "https://www.greda.design/#states",
      "search": [
        "states",
        "States",
        "Error, required, readonly and disabled.",
        "error",
        "required",
        "readonly",
        "disabled"
      ],
      "classes": [
        "gr-field",
        "gr-field--invalid",
        "gr-field__control",
        "gr-field__label",
        "gr-field__message"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "error",
        "required",
        "readonly",
        "disabled"
      ],
      "requires": [],
      "accessibility": [
        "Use aria-invalid for errors.",
        "Do not communicate state with color alone."
      ],
      "implementation": {
        "html": "<label class=\"gr-field gr-field--invalid\">\n  <span class=\"gr-field__label\">Email</span>\n  <span class=\"gr-field__control\">\n    <input type=\"email\" value=\"luis@\" aria-invalid=\"true\" aria-describedby=\"email-error\" required>\n  </span>\n  <span class=\"gr-field__message\" id=\"email-error\">The domain after the @ is missing.</span>\n</label>\n<label class=\"gr-field\">\n  <span class=\"gr-field__label\">Prefix</span>\n  <span class=\"gr-field__control\">\n    <input type=\"text\" value=\"gr-\" readonly>\n  </span>\n</label>\n<label class=\"gr-field\">\n  <span class=\"gr-field__label\">Build step</span>\n  <span class=\"gr-field__control\">\n    <input type=\"text\" value=\"None\" disabled>\n  </span>\n</label>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the States component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<label class=\"gr-field gr-field--invalid\">\n  <span class=\"gr-field__label\">Email</span>\n  <span class=\"gr-field__control\">\n    <input type=\"email\" value=\"luis@\" aria-invalid=\"true\" aria-describedby=\"email-error\" required>\n  </span>\n  <span class=\"gr-field__message\" id=\"email-error\">The domain after the @ is missing.</span>\n</label>\n<label class=\"gr-field\">\n  <span class=\"gr-field__label\">Prefix</span>\n  <span class=\"gr-field__control\">\n    <input type=\"text\" value=\"gr-\" readonly>\n  </span>\n</label>\n<label class=\"gr-field\">\n  <span class=\"gr-field__label\">Build step</span>\n  <span class=\"gr-field__control\">\n    <input type=\"text\" value=\"None\" disabled>\n  </span>\n</label>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use aria-invalid for errors.\n- Do not communicate state with color alone.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "card",
      "name": "Card",
      "category": "Data",
      "description": "A surface, not decoration.",
      "docs": "https://www.greda.design/#card",
      "search": [
        "card",
        "Card",
        "A surface, not decoration."
      ],
      "classes": [
        "gr-button",
        "gr-button--primary",
        "gr-button--quiet",
        "gr-card",
        "gr-card__actions",
        "gr-card__body",
        "gr-card__title"
      ],
      "tokens": [],
      "icons": [],
      "states": [],
      "requires": [],
      "accessibility": [
        "Preserve semantic HTML.",
        "Keep visible focus on interactive elements."
      ],
      "implementation": {
        "html": "<article class=\"gr-card demo-card\">\n  <h3 class=\"gr-card__title\">Personal project</h3>\n  <p class=\"gr-card__body\">A surface grouped only when the content needs a boundary you can recognise.</p>\n  <div class=\"gr-card__actions\">\n    <button class=\"gr-button gr-button--primary\" type=\"button\">Open</button>\n    <button class=\"gr-button gr-button--quiet\" type=\"button\">Duplicate</button>\n  </div>\n</article>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Card component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<article class=\"gr-card demo-card\">\n  <h3 class=\"gr-card__title\">Personal project</h3>\n  <p class=\"gr-card__body\">A surface grouped only when the content needs a boundary you can recognise.</p>\n  <div class=\"gr-card__actions\">\n    <button class=\"gr-button gr-button--primary\" type=\"button\">Open</button>\n    <button class=\"gr-button gr-button--quiet\" type=\"button\">Duplicate</button>\n  </div>\n</article>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Preserve semantic HTML.\n- Keep visible focus on interactive elements.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "badge",
      "name": "Badge",
      "category": "Data",
      "description": "A short label, in mono like everything technical.",
      "docs": "https://www.greda.design/#badge",
      "search": [
        "badge",
        "Badge",
        "A short label, in mono like everything technical."
      ],
      "classes": [
        "gr-badge",
        "gr-badge--accent",
        "gr-badge--strong",
        "gr-cluster"
      ],
      "tokens": [],
      "icons": [],
      "states": [],
      "requires": [],
      "accessibility": [
        "Preserve semantic HTML.",
        "Keep visible focus on interactive elements."
      ],
      "implementation": {
        "html": "<div class=\"gr-cluster\">\n  <span class=\"gr-badge\">v0.7.0</span>\n  <span class=\"gr-badge gr-badge--strong\">Stable</span>\n  <span class=\"gr-badge gr-badge--accent\">API change</span>\n</div>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Badge component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-cluster\">\n  <span class=\"gr-badge\">v0.7.0</span>\n  <span class=\"gr-badge gr-badge--strong\">Stable</span>\n  <span class=\"gr-badge gr-badge--accent\">API change</span>\n</div>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Preserve semantic HTML.\n- Keep visible focus on interactive elements.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "table",
      "name": "Table",
      "category": "Data",
      "description": "Rules that separate; numbers in mono and to the right.",
      "docs": "https://www.greda.design/#table",
      "search": [
        "table",
        "Table",
        "Rules that separate; numbers in mono and to the right."
      ],
      "classes": [
        "gr-table",
        "gr-table-wrap",
        "gr-table__numeric"
      ],
      "tokens": [],
      "icons": [],
      "states": [],
      "requires": [],
      "accessibility": [
        "Preserve semantic HTML.",
        "Keep visible focus on interactive elements."
      ],
      "implementation": {
        "html": "<div class=\"gr-table-wrap\">\n  <table class=\"gr-table\">\n    <thead>\n      <tr><th>Control</th><th>Compact</th><th class=\"gr-table__numeric\">Touch</th></tr>\n    </thead>\n    <tbody>\n      <tr><td>Button</td><td>32px</td><td class=\"gr-table__numeric\">44px</td></tr>\n      <tr><td>Field</td><td>32px</td><td class=\"gr-table__numeric\">44px</td></tr>\n      <tr><td>Choice</td><td>20px</td><td class=\"gr-table__numeric\">44px</td></tr>\n    </tbody>\n  </table>\n</div>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Table component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-table-wrap\">\n  <table class=\"gr-table\">\n    <thead>\n      <tr><th>Control</th><th>Compact</th><th class=\"gr-table__numeric\">Touch</th></tr>\n    </thead>\n    <tbody>\n      <tr><td>Button</td><td>32px</td><td class=\"gr-table__numeric\">44px</td></tr>\n      <tr><td>Field</td><td>32px</td><td class=\"gr-table__numeric\">44px</td></tr>\n      <tr><td>Choice</td><td>20px</td><td class=\"gr-table__numeric\">44px</td></tr>\n    </tbody>\n  </table>\n</div>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Preserve semantic HTML.\n- Keep visible focus on interactive elements.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "prose",
      "name": "Prose + Code",
      "category": "Content",
      "description": "Long-form content and technical snippets with one reading rhythm.",
      "docs": "https://www.greda.design/#prose",
      "search": [
        "prose",
        "Prose + Code",
        "Long-form content and technical snippets with one reading rhythm.",
        "article",
        "long form",
        "code snippet"
      ],
      "classes": [
        "gr-button",
        "gr-button--primary",
        "gr-code",
        "gr-code-snippet",
        "gr-code-snippet__copy",
        "gr-code-snippet__head",
        "gr-codeblock",
        "gr-prose",
        "gr-prose__lead"
      ],
      "tokens": [],
      "icons": [],
      "states": [],
      "requires": [],
      "accessibility": [
        "Preserve semantic HTML.",
        "Keep visible focus on interactive elements."
      ],
      "implementation": {
        "html": "<div class=\"gr-prose\">\n                  <p class=\"gr-prose__lead\">Greda grows from evidence, not speculation. A pattern enters the system only after it repeats across <a href=\"#components\">real projects</a>.</p>\n                  <p>That keeps the stylesheet small enough to understand and makes each public class carry a clear responsibility.</p>\n                  <h3>Before a pattern enters</h3>\n                  <ul>\n                    <li>Complete interactive states, <code class=\"gr-code\">:focus-visible</code> included.</li>\n                    <li>Mobile behaviour from 360px up.</li>\n                    <li>Tokens instead of repeated values.</li>\n                  </ul>\n                  <blockquote>\n                    <p>The component should explain its purpose before its decoration.</p>\n                    <cite>Greda contribution principle</cite>\n                  </blockquote>\n                  <figure class=\"gr-code-snippet\">\n                    <figcaption class=\"gr-code-snippet__head\">\n                      <span>HTML</span>\n                      <button class=\"gr-code-snippet__copy\" type=\"button\" data-copy-code>Copy</button>\n                    </figcaption>\n                    <pre class=\"gr-codeblock\"><code>&lt;link rel=\"stylesheet\" href=\"/v0.7.0/greda.css\"&gt;\n\n&lt;button class=\"gr-button gr-button--primary\"&gt;Save&lt;/button&gt;</code></pre>\n                  </figure>\n                </div>",
        "javascript": "document.querySelectorAll(\"[data-copy-code]\").forEach((button) => {\n  button.addEventListener(\"click\", async () => {\n    const code = button.closest(\".gr-code-snippet\")?.querySelector(\"code\");\n    if (code) await navigator.clipboard.writeText(code.textContent);\n  });\n});",
        "aiPrompt": "Implement the Prose + Code component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-prose\">\n                  <p class=\"gr-prose__lead\">Greda grows from evidence, not speculation. A pattern enters the system only after it repeats across <a href=\"#components\">real projects</a>.</p>\n                  <p>That keeps the stylesheet small enough to understand and makes each public class carry a clear responsibility.</p>\n                  <h3>Before a pattern enters</h3>\n                  <ul>\n                    <li>Complete interactive states, <code class=\"gr-code\">:focus-visible</code> included.</li>\n                    <li>Mobile behaviour from 360px up.</li>\n                    <li>Tokens instead of repeated values.</li>\n                  </ul>\n                  <blockquote>\n                    <p>The component should explain its purpose before its decoration.</p>\n                    <cite>Greda contribution principle</cite>\n                  </blockquote>\n                  <figure class=\"gr-code-snippet\">\n                    <figcaption class=\"gr-code-snippet__head\">\n                      <span>HTML</span>\n                      <button class=\"gr-code-snippet__copy\" type=\"button\" data-copy-code>Copy</button>\n                    </figcaption>\n                    <pre class=\"gr-codeblock\"><code>&lt;link rel=\"stylesheet\" href=\"/v0.7.0/greda.css\"&gt;\n\n&lt;button class=\"gr-button gr-button--primary\"&gt;Save&lt;/button&gt;</code></pre>\n                  </figure>\n                </div>\n```\n\nUse this JavaScript behavior:\n\n```js\ndocument.querySelectorAll(\"[data-copy-code]\").forEach((button) => {\n  button.addEventListener(\"click\", async () => {\n    const code = button.closest(\".gr-code-snippet\")?.querySelector(\"code\");\n    if (code) await navigator.clipboard.writeText(code.textContent);\n  });\n});\n```\n\nAccessibility requirements:\n- Preserve semantic HTML.\n- Keep visible focus on interactive elements.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "dialog",
      "name": "Dialog",
      "category": "Overlays",
      "description": "Native <dialog> : trapped focus and Escape for free.",
      "docs": "https://www.greda.design/#dialog",
      "search": [
        "dialog",
        "Dialog",
        "Native <dialog> : trapped focus and Escape for free.",
        "modal",
        "confirmation"
      ],
      "classes": [
        "gr-button",
        "gr-button--primary",
        "gr-button--quiet",
        "gr-button--secondary",
        "gr-dialog",
        "gr-dialog__actions",
        "gr-dialog__body",
        "gr-dialog__head",
        "gr-dialog__title",
        "gr-icon"
      ],
      "tokens": [],
      "icons": [
        "close"
      ],
      "states": [
        "closed",
        "open"
      ],
      "requires": [
        "Dialog API"
      ],
      "accessibility": [
        "Use a native dialog opened modally.",
        "Give icon-only close actions an accessible name."
      ],
      "implementation": {
        "html": "<button class=\"gr-button gr-button--secondary\" type=\"button\" command=\"show-modal\" commandfor=\"demo-dialog\">\n  Open dialog\n</button>\n<dialog class=\"gr-dialog\" id=\"demo-dialog\">\n  <div class=\"gr-dialog__head\">\n    <h3 class=\"gr-dialog__title\">Discard the changes</h3>\n    <button class=\"gr-button gr-button--quiet\" type=\"button\" command=\"close\" commandfor=\"demo-dialog\" aria-label=\"Close\">\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M18 6L6 18\"></path>\n        <path d=\"M6 6L18 18\"></path>\n      </svg>\n    </button>\n  </div>\n  <p class=\"gr-dialog__body\">Everything edited since the last save will be lost. This cannot be undone.</p>\n  <div class=\"gr-dialog__actions\">\n    <button class=\"gr-button gr-button--quiet\" type=\"button\" command=\"close\" commandfor=\"demo-dialog\">Cancel</button>\n    <button class=\"gr-button gr-button--primary\" type=\"button\" command=\"close\" commandfor=\"demo-dialog\">Discard</button>\n  </div>\n</dialog>",
        "javascript": "if (!(\"command\" in HTMLButtonElement.prototype)) {\n  document.querySelectorAll(\"button[command][commandfor]\").forEach((button) => {\n    button.addEventListener(\"click\", () => {\n      const dialog = document.getElementById(button.getAttribute(\"commandfor\"));\n      if (!(dialog instanceof HTMLDialogElement)) return;\n      if (button.getAttribute(\"command\") === \"show-modal\") dialog.showModal();\n      if (button.getAttribute(\"command\") === \"close\") dialog.close();\n    });\n  });\n}",
        "aiPrompt": "Implement the Dialog component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<button class=\"gr-button gr-button--secondary\" type=\"button\" command=\"show-modal\" commandfor=\"demo-dialog\">\n  Open dialog\n</button>\n<dialog class=\"gr-dialog\" id=\"demo-dialog\">\n  <div class=\"gr-dialog__head\">\n    <h3 class=\"gr-dialog__title\">Discard the changes</h3>\n    <button class=\"gr-button gr-button--quiet\" type=\"button\" command=\"close\" commandfor=\"demo-dialog\" aria-label=\"Close\">\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M18 6L6 18\"></path>\n        <path d=\"M6 6L18 18\"></path>\n      </svg>\n    </button>\n  </div>\n  <p class=\"gr-dialog__body\">Everything edited since the last save will be lost. This cannot be undone.</p>\n  <div class=\"gr-dialog__actions\">\n    <button class=\"gr-button gr-button--quiet\" type=\"button\" command=\"close\" commandfor=\"demo-dialog\">Cancel</button>\n    <button class=\"gr-button gr-button--primary\" type=\"button\" command=\"close\" commandfor=\"demo-dialog\">Discard</button>\n  </div>\n</dialog>\n```\n\nUse this JavaScript behavior:\n\n```js\nif (!(\"command\" in HTMLButtonElement.prototype)) {\n  document.querySelectorAll(\"button[command][commandfor]\").forEach((button) => {\n    button.addEventListener(\"click\", () => {\n      const dialog = document.getElementById(button.getAttribute(\"commandfor\"));\n      if (!(dialog instanceof HTMLDialogElement)) return;\n      if (button.getAttribute(\"command\") === \"show-modal\") dialog.showModal();\n      if (button.getAttribute(\"command\") === \"close\") dialog.close();\n    });\n  });\n}\n```\n\nAccessibility requirements:\n- Use a native dialog opened modally.\n- Give icon-only close actions an accessible name.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "drawer",
      "name": "Drawer",
      "category": "Overlays",
      "description": "A modal side panel for focused tasks that should preserve the current page.",
      "docs": "https://www.greda.design/#drawer",
      "search": [
        "drawer",
        "Drawer",
        "A modal side panel for focused tasks that should preserve the current page.",
        "side panel",
        "sheet",
        "filters"
      ],
      "classes": [
        "gr-button",
        "gr-button--icon",
        "gr-button--primary",
        "gr-button--quiet",
        "gr-button--secondary",
        "gr-choice",
        "gr-choice__input",
        "gr-choice__label",
        "gr-drawer",
        "gr-drawer__actions",
        "gr-drawer__body",
        "gr-drawer__head",
        "gr-drawer__title",
        "gr-fieldset",
        "gr-fieldset__legend",
        "gr-icon"
      ],
      "tokens": [],
      "icons": [
        "close"
      ],
      "states": [
        "closed",
        "open"
      ],
      "requires": [
        "Dialog API",
        "Grid22"
      ],
      "accessibility": [
        "Use a native dialog opened modally so focus, Escape, and page inertness remain intact.",
        "Give the drawer an accessible title and every icon-only close action an accessible name.",
        "Keep the body independently scrollable while its title and actions remain visible."
      ],
      "implementation": {
        "html": "<button class=\"gr-button gr-button--secondary\" type=\"button\" command=\"show-modal\" commandfor=\"demo-drawer\">\n  Filter projects\n</button>\n<dialog class=\"gr-drawer\" id=\"demo-drawer\" aria-labelledby=\"demo-drawer-title\">\n  <div class=\"gr-drawer__head\">\n    <h3 class=\"gr-drawer__title\" id=\"demo-drawer-title\">Filter projects</h3>\n    <button class=\"gr-button gr-button--quiet gr-button--icon\" type=\"button\" command=\"close\" commandfor=\"demo-drawer\" aria-label=\"Close filters\">\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M18 6L6 18\"></path>\n        <path d=\"M6 6L18 18\"></path>\n      </svg>\n    </button>\n  </div>\n  <div class=\"gr-drawer__body\">\n    <fieldset class=\"gr-fieldset\">\n      <legend class=\"gr-fieldset__legend\">Status</legend>\n      <label class=\"gr-choice\">\n        <input class=\"gr-choice__input\" type=\"checkbox\" name=\"project-status\" value=\"active\" checked>\n        <span class=\"gr-choice__label\">Active</span>\n      </label>\n      <label class=\"gr-choice\">\n        <input class=\"gr-choice__input\" type=\"checkbox\" name=\"project-status\" value=\"paused\">\n        <span class=\"gr-choice__label\">Paused</span>\n      </label>\n      <label class=\"gr-choice\">\n        <input class=\"gr-choice__input\" type=\"checkbox\" name=\"project-status\" value=\"archived\">\n        <span class=\"gr-choice__label\">Archived</span>\n      </label>\n    </fieldset>\n  </div>\n  <div class=\"gr-drawer__actions\">\n    <button class=\"gr-button gr-button--primary\" type=\"button\" command=\"close\" commandfor=\"demo-drawer\">Apply filters</button>\n  </div>\n</dialog>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Drawer component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<button class=\"gr-button gr-button--secondary\" type=\"button\" command=\"show-modal\" commandfor=\"demo-drawer\">\n  Filter projects\n</button>\n<dialog class=\"gr-drawer\" id=\"demo-drawer\" aria-labelledby=\"demo-drawer-title\">\n  <div class=\"gr-drawer__head\">\n    <h3 class=\"gr-drawer__title\" id=\"demo-drawer-title\">Filter projects</h3>\n    <button class=\"gr-button gr-button--quiet gr-button--icon\" type=\"button\" command=\"close\" commandfor=\"demo-drawer\" aria-label=\"Close filters\">\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M18 6L6 18\"></path>\n        <path d=\"M6 6L18 18\"></path>\n      </svg>\n    </button>\n  </div>\n  <div class=\"gr-drawer__body\">\n    <fieldset class=\"gr-fieldset\">\n      <legend class=\"gr-fieldset__legend\">Status</legend>\n      <label class=\"gr-choice\">\n        <input class=\"gr-choice__input\" type=\"checkbox\" name=\"project-status\" value=\"active\" checked>\n        <span class=\"gr-choice__label\">Active</span>\n      </label>\n      <label class=\"gr-choice\">\n        <input class=\"gr-choice__input\" type=\"checkbox\" name=\"project-status\" value=\"paused\">\n        <span class=\"gr-choice__label\">Paused</span>\n      </label>\n      <label class=\"gr-choice\">\n        <input class=\"gr-choice__input\" type=\"checkbox\" name=\"project-status\" value=\"archived\">\n        <span class=\"gr-choice__label\">Archived</span>\n      </label>\n    </fieldset>\n  </div>\n  <div class=\"gr-drawer__actions\">\n    <button class=\"gr-button gr-button--primary\" type=\"button\" command=\"close\" commandfor=\"demo-drawer\">Apply filters</button>\n  </div>\n</dialog>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use a native dialog opened modally so focus, Escape, and page inertness remain intact.\n- Give the drawer an accessible title and every icon-only close action an accessible name.\n- Keep the body independently scrollable while its title and actions remain visible.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "command-menu",
      "name": "Search / Command Menu",
      "category": "Overlays",
      "description": "Find and run a known action without leaving the keyboard.",
      "docs": "https://www.greda.design/#command-menu",
      "search": [
        "command-menu",
        "Search / Command Menu",
        "Find and run a known action without leaving the keyboard.",
        "search",
        "command palette",
        "quick actions",
        "launcher"
      ],
      "classes": [
        "gr-button",
        "gr-button--icon",
        "gr-button--quiet",
        "gr-button--secondary",
        "gr-command-menu",
        "gr-command-menu__empty",
        "gr-command-menu__input",
        "gr-command-menu__item",
        "gr-command-menu__meta",
        "gr-command-menu__results",
        "gr-command-menu__search",
        "gr-icon",
        "gr-visually-hidden"
      ],
      "tokens": [],
      "icons": [
        "search",
        "close"
      ],
      "states": [
        "closed",
        "open",
        "filtered",
        "selected",
        "empty"
      ],
      "requires": [
        "Dialog API",
        "Grid22",
        "Command menu script"
      ],
      "accessibility": [
        "Keep focus in the combobox and expose the selected result through aria-activedescendant.",
        "Support Arrow Up, Arrow Down, Enter, and Escape without overriding native text editing.",
        "Announce the result count and provide an explicit empty state."
      ],
      "implementation": {
        "html": "<button class=\"gr-button gr-button--secondary\" type=\"button\" command=\"show-modal\" commandfor=\"demo-command-menu\">\n  Search commands\n</button>\n<dialog class=\"gr-command-menu\" id=\"demo-command-menu\" aria-labelledby=\"demo-command-menu-title\" data-gr-command-menu>\n  <h3 class=\"gr-visually-hidden\" id=\"demo-command-menu-title\">Search commands</h3>\n  <div class=\"gr-command-menu__search\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <circle cx=\"9.82\" cy=\"9.82\" r=\"6.67\"></circle>\n      <path d=\"M15.05 15.05L20.85 20.85\"></path>\n    </svg>\n    <input class=\"gr-command-menu__input\" id=\"demo-command-search\" type=\"text\" role=\"combobox\" aria-label=\"Search commands\" placeholder=\"Type a command…\" autocomplete=\"off\" aria-autocomplete=\"list\" aria-expanded=\"false\" aria-controls=\"demo-command-results\" aria-activedescendant=\"command-foundations\" data-gr-command-input autofocus>\n    <button class=\"gr-button gr-button--quiet gr-button--icon\" type=\"button\" command=\"close\" commandfor=\"demo-command-menu\" aria-label=\"Close command menu\">\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M18 6L6 18\"></path>\n        <path d=\"M6 6L18 18\"></path>\n      </svg>\n    </button>\n  </div>\n  <div class=\"gr-command-menu__results\" id=\"demo-command-results\" role=\"listbox\" aria-label=\"Commands\">\n    <a class=\"gr-command-menu__item\" id=\"command-foundations\" href=\"#foundations\" role=\"option\" aria-selected=\"true\" data-gr-command-option data-search=\"tokens color type spacing\">\n      <span>Go to foundations</span>\n      <span class=\"gr-command-menu__meta\">Navigate</span>\n    </a>\n    <a class=\"gr-command-menu__item\" id=\"command-components\" href=\"#components\" role=\"option\" aria-selected=\"false\" data-gr-command-option data-search=\"components primitives patterns\">\n      <span>Browse components</span>\n      <span class=\"gr-command-menu__meta\">Navigate</span>\n    </a>\n    <a class=\"gr-command-menu__item\" id=\"command-releases\" href=\"./releases.html\" role=\"option\" aria-selected=\"false\" data-gr-command-option data-search=\"release notes versions changelog\">\n      <span>Read release notes</span>\n      <span class=\"gr-command-menu__meta\">Navigate</span>\n    </a>\n    <a class=\"gr-command-menu__item\" id=\"command-ai\" href=\"#ai-usage\" role=\"option\" aria-selected=\"false\" data-gr-command-option data-search=\"ai agents llms registry\">\n      <span>Use Greda with AI</span>\n      <span class=\"gr-command-menu__meta\">Navigate</span>\n    </a>\n  </div>\n  <p class=\"gr-command-menu__empty\" data-gr-command-empty hidden>No commands found.</p>\n  <p class=\"gr-visually-hidden\" data-gr-command-status aria-live=\"polite\">4 commands available.</p>\n</dialog>",
        "javascript": "document.querySelectorAll(\"[data-gr-command-menu]\").forEach((dialog) => {\n  const input = dialog.querySelector(\"[data-gr-command-input]\");\n  const results = dialog.querySelector('[role=\"listbox\"]');\n  const options = [...dialog.querySelectorAll(\"[data-gr-command-option]\")];\n  const empty = dialog.querySelector(\"[data-gr-command-empty]\");\n  const status = dialog.querySelector(\"[data-gr-command-status]\");\n  const trigger = document.querySelector(\n    `[command=\"show-modal\"][commandfor=\"${CSS.escape(dialog.id)}\"]`,\n  );\n  if (!input || !results || !empty || !status) return;\n\n  const visibleOptions = () => options.filter((option) => !option.hidden);\n\n  function select(option) {\n    options.forEach((candidate) => {\n      candidate.setAttribute(\"aria-selected\", String(candidate === option));\n    });\n    if (option) input.setAttribute(\"aria-activedescendant\", option.id);\n    else input.removeAttribute(\"aria-activedescendant\");\n  }\n\n  function filter() {\n    const query = input.value.trim().toLocaleLowerCase();\n    options.forEach((option) => {\n      const text = `${option.textContent} ${option.dataset.search || \"\"}`.toLocaleLowerCase();\n      option.hidden = !text.includes(query);\n    });\n    const available = visibleOptions();\n    results.hidden = available.length === 0;\n    empty.hidden = available.length !== 0;\n    status.textContent = available.length\n      ? `${available.length} ${available.length === 1 ? \"command\" : \"commands\"} available.`\n      : \"No commands found.\";\n    select(available[0]);\n  }\n\n  input.addEventListener(\"input\", filter);\n  input.addEventListener(\"keydown\", (event) => {\n    const available = visibleOptions();\n    const current = available.findIndex(\n      (option) => option.getAttribute(\"aria-selected\") === \"true\",\n    );\n    if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n      if (!available.length) return;\n      event.preventDefault();\n      const direction = event.key === \"ArrowDown\" ? 1 : -1;\n      const index = (current + direction + available.length) % available.length;\n      select(available[index]);\n      available[index]?.scrollIntoView({ block: \"nearest\" });\n    }\n    if (event.key === \"Enter\") {\n      const selected = available.find(\n        (option) => option.getAttribute(\"aria-selected\") === \"true\",\n      );\n      if (selected) {\n        event.preventDefault();\n        selected.click();\n      }\n    }\n    if (event.key === \"Escape\") {\n      event.preventDefault();\n      dialog.close();\n      trigger?.focus();\n    }\n  });\n\n  options.forEach((option) => {\n    option.addEventListener(\"click\", () => dialog.close());\n  });\n\n  trigger?.addEventListener(\"click\", () => {\n    requestAnimationFrame(() => {\n      if (!dialog.open) return;\n      input.setAttribute(\"aria-expanded\", \"true\");\n      filter();\n      input.focus();\n    });\n  });\n\n  dialog.addEventListener(\"close\", () => {\n    input.setAttribute(\"aria-expanded\", \"false\");\n    input.value = \"\";\n    filter();\n  });\n});",
        "aiPrompt": "Implement the Search / Command Menu component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<button class=\"gr-button gr-button--secondary\" type=\"button\" command=\"show-modal\" commandfor=\"demo-command-menu\">\n  Search commands\n</button>\n<dialog class=\"gr-command-menu\" id=\"demo-command-menu\" aria-labelledby=\"demo-command-menu-title\" data-gr-command-menu>\n  <h3 class=\"gr-visually-hidden\" id=\"demo-command-menu-title\">Search commands</h3>\n  <div class=\"gr-command-menu__search\">\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <circle cx=\"9.82\" cy=\"9.82\" r=\"6.67\"></circle>\n      <path d=\"M15.05 15.05L20.85 20.85\"></path>\n    </svg>\n    <input class=\"gr-command-menu__input\" id=\"demo-command-search\" type=\"text\" role=\"combobox\" aria-label=\"Search commands\" placeholder=\"Type a command…\" autocomplete=\"off\" aria-autocomplete=\"list\" aria-expanded=\"false\" aria-controls=\"demo-command-results\" aria-activedescendant=\"command-foundations\" data-gr-command-input autofocus>\n    <button class=\"gr-button gr-button--quiet gr-button--icon\" type=\"button\" command=\"close\" commandfor=\"demo-command-menu\" aria-label=\"Close command menu\">\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M18 6L6 18\"></path>\n        <path d=\"M6 6L18 18\"></path>\n      </svg>\n    </button>\n  </div>\n  <div class=\"gr-command-menu__results\" id=\"demo-command-results\" role=\"listbox\" aria-label=\"Commands\">\n    <a class=\"gr-command-menu__item\" id=\"command-foundations\" href=\"#foundations\" role=\"option\" aria-selected=\"true\" data-gr-command-option data-search=\"tokens color type spacing\">\n      <span>Go to foundations</span>\n      <span class=\"gr-command-menu__meta\">Navigate</span>\n    </a>\n    <a class=\"gr-command-menu__item\" id=\"command-components\" href=\"#components\" role=\"option\" aria-selected=\"false\" data-gr-command-option data-search=\"components primitives patterns\">\n      <span>Browse components</span>\n      <span class=\"gr-command-menu__meta\">Navigate</span>\n    </a>\n    <a class=\"gr-command-menu__item\" id=\"command-releases\" href=\"./releases.html\" role=\"option\" aria-selected=\"false\" data-gr-command-option data-search=\"release notes versions changelog\">\n      <span>Read release notes</span>\n      <span class=\"gr-command-menu__meta\">Navigate</span>\n    </a>\n    <a class=\"gr-command-menu__item\" id=\"command-ai\" href=\"#ai-usage\" role=\"option\" aria-selected=\"false\" data-gr-command-option data-search=\"ai agents llms registry\">\n      <span>Use Greda with AI</span>\n      <span class=\"gr-command-menu__meta\">Navigate</span>\n    </a>\n  </div>\n  <p class=\"gr-command-menu__empty\" data-gr-command-empty hidden>No commands found.</p>\n  <p class=\"gr-visually-hidden\" data-gr-command-status aria-live=\"polite\">4 commands available.</p>\n</dialog>\n```\n\nUse this JavaScript behavior:\n\n```js\ndocument.querySelectorAll(\"[data-gr-command-menu]\").forEach((dialog) => {\n  const input = dialog.querySelector(\"[data-gr-command-input]\");\n  const results = dialog.querySelector('[role=\"listbox\"]');\n  const options = [...dialog.querySelectorAll(\"[data-gr-command-option]\")];\n  const empty = dialog.querySelector(\"[data-gr-command-empty]\");\n  const status = dialog.querySelector(\"[data-gr-command-status]\");\n  const trigger = document.querySelector(\n    `[command=\"show-modal\"][commandfor=\"${CSS.escape(dialog.id)}\"]`,\n  );\n  if (!input || !results || !empty || !status) return;\n\n  const visibleOptions = () => options.filter((option) => !option.hidden);\n\n  function select(option) {\n    options.forEach((candidate) => {\n      candidate.setAttribute(\"aria-selected\", String(candidate === option));\n    });\n    if (option) input.setAttribute(\"aria-activedescendant\", option.id);\n    else input.removeAttribute(\"aria-activedescendant\");\n  }\n\n  function filter() {\n    const query = input.value.trim().toLocaleLowerCase();\n    options.forEach((option) => {\n      const text = `${option.textContent} ${option.dataset.search || \"\"}`.toLocaleLowerCase();\n      option.hidden = !text.includes(query);\n    });\n    const available = visibleOptions();\n    results.hidden = available.length === 0;\n    empty.hidden = available.length !== 0;\n    status.textContent = available.length\n      ? `${available.length} ${available.length === 1 ? \"command\" : \"commands\"} available.`\n      : \"No commands found.\";\n    select(available[0]);\n  }\n\n  input.addEventListener(\"input\", filter);\n  input.addEventListener(\"keydown\", (event) => {\n    const available = visibleOptions();\n    const current = available.findIndex(\n      (option) => option.getAttribute(\"aria-selected\") === \"true\",\n    );\n    if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n      if (!available.length) return;\n      event.preventDefault();\n      const direction = event.key === \"ArrowDown\" ? 1 : -1;\n      const index = (current + direction + available.length) % available.length;\n      select(available[index]);\n      available[index]?.scrollIntoView({ block: \"nearest\" });\n    }\n    if (event.key === \"Enter\") {\n      const selected = available.find(\n        (option) => option.getAttribute(\"aria-selected\") === \"true\",\n      );\n      if (selected) {\n        event.preventDefault();\n        selected.click();\n      }\n    }\n    if (event.key === \"Escape\") {\n      event.preventDefault();\n      dialog.close();\n      trigger?.focus();\n    }\n  });\n\n  options.forEach((option) => {\n    option.addEventListener(\"click\", () => dialog.close());\n  });\n\n  trigger?.addEventListener(\"click\", () => {\n    requestAnimationFrame(() => {\n      if (!dialog.open) return;\n      input.setAttribute(\"aria-expanded\", \"true\");\n      filter();\n      input.focus();\n    });\n  });\n\n  dialog.addEventListener(\"close\", () => {\n    input.setAttribute(\"aria-expanded\", \"false\");\n    input.value = \"\";\n    filter();\n  });\n});\n```\n\nAccessibility requirements:\n- Keep focus in the combobox and expose the selected result through aria-activedescendant.\n- Support Arrow Up, Arrow Down, Enter, and Escape without overriding native text editing.\n- Announce the result count and provide an explicit empty state.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "disclosure",
      "name": "Disclosure",
      "category": "Content",
      "description": "<details> ; the panel does not animate.",
      "docs": "https://www.greda.design/#disclosure",
      "search": [
        "disclosure",
        "Disclosure",
        "<details> ; the panel does not animate.",
        "accordion",
        "details"
      ],
      "classes": [
        "gr-code",
        "gr-disclosure",
        "gr-disclosure__panel",
        "gr-disclosure__summary",
        "gr-icon"
      ],
      "tokens": [],
      "icons": [
        "chevron-down"
      ],
      "states": [
        "closed",
        "open"
      ],
      "requires": [],
      "accessibility": [
        "Use native details and summary elements."
      ],
      "implementation": {
        "html": "<details class=\"gr-disclosure\">\n  <summary class=\"gr-disclosure__summary\">\n    Why is there no build step?\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M6 9l6 6 6-6\"></path>\n    </svg>\n  </summary>\n  <p class=\"gr-disclosure__panel\">Because a person should be able to copy the <code class=\"gr-code\">styles/</code> folder and keep working without installing anything.</p>\n</details>\n<details class=\"gr-disclosure\">\n  <summary class=\"gr-disclosure__summary\">\n    When does a component get in?\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M6 9l6 6 6-6\"></path>\n    </svg>\n  </summary>\n  <p class=\"gr-disclosure__panel\">Once it has shown up as a real pattern in more than one project.</p>\n</details>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Disclosure component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<details class=\"gr-disclosure\">\n  <summary class=\"gr-disclosure__summary\">\n    Why is there no build step?\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M6 9l6 6 6-6\"></path>\n    </svg>\n  </summary>\n  <p class=\"gr-disclosure__panel\">Because a person should be able to copy the <code class=\"gr-code\">styles/</code> folder and keep working without installing anything.</p>\n</details>\n<details class=\"gr-disclosure\">\n  <summary class=\"gr-disclosure__summary\">\n    When does a component get in?\n    <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <path d=\"M6 9l6 6 6-6\"></path>\n    </svg>\n  </summary>\n  <p class=\"gr-disclosure__panel\">Once it has shown up as a real pattern in more than one project.</p>\n</details>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use native details and summary elements.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "tabs",
      "name": "Tabs",
      "category": "Navigation",
      "description": "The styling lives here; keeping aria-selected in sync is the consumer's.",
      "docs": "https://www.greda.design/#tabs",
      "search": [
        "tabs",
        "Tabs",
        "The styling lives here; keeping aria-selected in sync is the consumer's.",
        "tabpanel",
        "tablist"
      ],
      "classes": [
        "gr-field__message",
        "gr-tabs",
        "gr-tabs__list",
        "gr-tabs__panel",
        "gr-tabs__tab"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "selected",
        "unselected"
      ],
      "requires": [
        "Roving tab script"
      ],
      "accessibility": [
        "Keep role=tab, aria-selected, tabindex, aria-controls, and panel visibility synchronized.",
        "Support Arrow Left/Right, Home, and End."
      ],
      "implementation": {
        "html": "<div class=\"gr-tabs\">\n  <div class=\"gr-tabs__list\" role=\"tablist\" aria-label=\"Control density\">\n    <button class=\"gr-tabs__tab\" type=\"button\" role=\"tab\" aria-selected=\"true\" id=\"tab-compact\" aria-controls=\"panel-compact\">Compact</button>\n    <button class=\"gr-tabs__tab\" type=\"button\" role=\"tab\" aria-selected=\"false\" id=\"tab-touch\" aria-controls=\"panel-touch\">Touch</button>\n  </div>\n  <div class=\"gr-tabs__panel\" role=\"tabpanel\" id=\"panel-compact\" aria-labelledby=\"tab-compact\">\n    <p class=\"gr-field__message\">32px with a precise pointer, from 45rem up.</p>\n  </div>\n  <div class=\"gr-tabs__panel\" role=\"tabpanel\" id=\"panel-touch\" aria-labelledby=\"tab-touch\" hidden>\n    <p class=\"gr-field__message\">44px everywhere else, so the target stays comfortable.</p>\n  </div>\n</div>",
        "javascript": "document.querySelectorAll(\".gr-tabs\").forEach((tabs) => {\n  const controls = [...tabs.querySelectorAll('[role=\"tab\"]')];\n\n  function activate(tab, focus = false) {\n    controls.forEach((control) => {\n      const selected = control === tab;\n      control.setAttribute(\"aria-selected\", String(selected));\n      control.tabIndex = selected ? 0 : -1;\n      const panel = tabs.querySelector(`#${control.getAttribute(\"aria-controls\")}`);\n      if (panel) panel.hidden = !selected;\n    });\n    if (focus) tab.focus();\n  }\n\n  controls.forEach((tab, index) => {\n    tab.addEventListener(\"click\", () => activate(tab));\n    tab.addEventListener(\"keydown\", (event) => {\n      let next;\n      if (event.key === \"ArrowRight\") next = controls[(index + 1) % controls.length];\n      if (event.key === \"ArrowLeft\") next = controls[(index - 1 + controls.length) % controls.length];\n      if (event.key === \"Home\") next = controls[0];\n      if (event.key === \"End\") next = controls.at(-1);\n      if (!next) return;\n      event.preventDefault();\n      activate(next, true);\n    });\n  });\n});",
        "aiPrompt": "Implement the Tabs component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-tabs\">\n  <div class=\"gr-tabs__list\" role=\"tablist\" aria-label=\"Control density\">\n    <button class=\"gr-tabs__tab\" type=\"button\" role=\"tab\" aria-selected=\"true\" id=\"tab-compact\" aria-controls=\"panel-compact\">Compact</button>\n    <button class=\"gr-tabs__tab\" type=\"button\" role=\"tab\" aria-selected=\"false\" id=\"tab-touch\" aria-controls=\"panel-touch\">Touch</button>\n  </div>\n  <div class=\"gr-tabs__panel\" role=\"tabpanel\" id=\"panel-compact\" aria-labelledby=\"tab-compact\">\n    <p class=\"gr-field__message\">32px with a precise pointer, from 45rem up.</p>\n  </div>\n  <div class=\"gr-tabs__panel\" role=\"tabpanel\" id=\"panel-touch\" aria-labelledby=\"tab-touch\" hidden>\n    <p class=\"gr-field__message\">44px everywhere else, so the target stays comfortable.</p>\n  </div>\n</div>\n```\n\nUse this JavaScript behavior:\n\n```js\ndocument.querySelectorAll(\".gr-tabs\").forEach((tabs) => {\n  const controls = [...tabs.querySelectorAll('[role=\"tab\"]')];\n\n  function activate(tab, focus = false) {\n    controls.forEach((control) => {\n      const selected = control === tab;\n      control.setAttribute(\"aria-selected\", String(selected));\n      control.tabIndex = selected ? 0 : -1;\n      const panel = tabs.querySelector(`#${control.getAttribute(\"aria-controls\")}`);\n      if (panel) panel.hidden = !selected;\n    });\n    if (focus) tab.focus();\n  }\n\n  controls.forEach((tab, index) => {\n    tab.addEventListener(\"click\", () => activate(tab));\n    tab.addEventListener(\"keydown\", (event) => {\n      let next;\n      if (event.key === \"ArrowRight\") next = controls[(index + 1) % controls.length];\n      if (event.key === \"ArrowLeft\") next = controls[(index - 1 + controls.length) % controls.length];\n      if (event.key === \"Home\") next = controls[0];\n      if (event.key === \"End\") next = controls.at(-1);\n      if (!next) return;\n      event.preventDefault();\n      activate(next, true);\n    });\n  });\n});\n```\n\nAccessibility requirements:\n- Keep role=tab, aria-selected, tabindex, aria-controls, and panel visibility synchronized.\n- Support Arrow Left/Right, Home, and End.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "vertical-steps",
      "name": "Vertical Steps",
      "category": "Navigation",
      "description": "Show progress through a sequence without hiding what comes next.",
      "docs": "https://www.greda.design/#vertical-steps",
      "search": [
        "vertical-steps",
        "Vertical Steps",
        "Show progress through a sequence without hiding what comes next.",
        "stepper",
        "workflow",
        "progress sequence"
      ],
      "classes": [
        "gr-icon",
        "gr-step",
        "gr-step--complete",
        "gr-step--current",
        "gr-step__content",
        "gr-step__description",
        "gr-step__marker",
        "gr-step__status",
        "gr-step__title",
        "gr-steps",
        "gr-steps--vertical"
      ],
      "tokens": [],
      "icons": [
        "check"
      ],
      "states": [
        "complete",
        "current",
        "upcoming"
      ],
      "requires": [],
      "accessibility": [
        "Use an ordered list and mark the active item with aria-current=step.",
        "Write state labels so completion is not communicated by color alone."
      ],
      "implementation": {
        "html": "<ol class=\"gr-steps gr-steps--vertical\" aria-label=\"Publish workflow\">\n  <li class=\"gr-step gr-step--complete\">\n    <span class=\"gr-step__marker\" aria-hidden=\"true\">\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M20 6L9 17l-5-5\"></path>\n      </svg>\n    </span>\n    <span class=\"gr-step__content\">\n      <span class=\"gr-step__title\">Draft</span>\n      <span class=\"gr-step__description\">Content and component API complete.</span>\n      <span class=\"gr-step__status\">Completed</span>\n    </span>\n  </li>\n  <li class=\"gr-step gr-step--current\" aria-current=\"step\">\n    <span class=\"gr-step__marker\" aria-hidden=\"true\">2</span>\n    <span class=\"gr-step__content\">\n      <span class=\"gr-step__title\">Review</span>\n      <span class=\"gr-step__description\">Accessibility and responsive behavior.</span>\n      <span class=\"gr-step__status\">Current step</span>\n    </span>\n  </li>\n  <li class=\"gr-step\">\n    <span class=\"gr-step__marker\" aria-hidden=\"true\">3</span>\n    <span class=\"gr-step__content\">\n      <span class=\"gr-step__title\">Publish</span>\n      <span class=\"gr-step__description\">Create the immutable release.</span>\n      <span class=\"gr-step__status\">Not started</span>\n    </span>\n  </li>\n</ol>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Vertical Steps component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<ol class=\"gr-steps gr-steps--vertical\" aria-label=\"Publish workflow\">\n  <li class=\"gr-step gr-step--complete\">\n    <span class=\"gr-step__marker\" aria-hidden=\"true\">\n      <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path d=\"M20 6L9 17l-5-5\"></path>\n      </svg>\n    </span>\n    <span class=\"gr-step__content\">\n      <span class=\"gr-step__title\">Draft</span>\n      <span class=\"gr-step__description\">Content and component API complete.</span>\n      <span class=\"gr-step__status\">Completed</span>\n    </span>\n  </li>\n  <li class=\"gr-step gr-step--current\" aria-current=\"step\">\n    <span class=\"gr-step__marker\" aria-hidden=\"true\">2</span>\n    <span class=\"gr-step__content\">\n      <span class=\"gr-step__title\">Review</span>\n      <span class=\"gr-step__description\">Accessibility and responsive behavior.</span>\n      <span class=\"gr-step__status\">Current step</span>\n    </span>\n  </li>\n  <li class=\"gr-step\">\n    <span class=\"gr-step__marker\" aria-hidden=\"true\">3</span>\n    <span class=\"gr-step__content\">\n      <span class=\"gr-step__title\">Publish</span>\n      <span class=\"gr-step__description\">Create the immutable release.</span>\n      <span class=\"gr-step__status\">Not started</span>\n    </span>\n  </li>\n</ol>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use an ordered list and mark the active item with aria-current=step.\n- Write state labels so completion is not communicated by color alone.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "popover",
      "name": "Popover",
      "category": "Overlays",
      "description": "The popover attribute: top layer and light dismiss.",
      "docs": "https://www.greda.design/#popover",
      "search": [
        "popover",
        "Popover",
        "The popover attribute: top layer and light dismiss.",
        "floating panel"
      ],
      "classes": [
        "gr-button",
        "gr-button--secondary",
        "gr-popover"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "closed",
        "open"
      ],
      "requires": [
        "Popover API"
      ],
      "accessibility": [
        "Use the native popover attribute for Escape and light dismiss."
      ],
      "implementation": {
        "html": "<button class=\"gr-button gr-button--secondary\" type=\"button\" popovertarget=\"demo-popover\">\n  What it validates\n</button>\n<div class=\"gr-popover\" id=\"demo-popover\" popover>\n  Local references, duplicate ids, inline styles, colours outside the tokens, and transitions on properties that are not composited.\n</div>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Popover component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<button class=\"gr-button gr-button--secondary\" type=\"button\" popovertarget=\"demo-popover\">\n  What it validates\n</button>\n<div class=\"gr-popover\" id=\"demo-popover\" popover>\n  Local references, duplicate ids, inline styles, colours outside the tokens, and transitions on properties that are not composited.\n</div>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use the native popover attribute for Escape and light dismiss.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "dropdown-menu",
      "name": "Dropdown Menu",
      "category": "Actions",
      "description": "A compact list of actions with roving keyboard focus and native dismissal.",
      "docs": "https://www.greda.design/#dropdown-menu",
      "search": [
        "dropdown-menu",
        "Dropdown Menu",
        "A compact list of actions with roving keyboard focus and native dismissal.",
        "action menu",
        "context menu",
        "menu"
      ],
      "classes": [
        "gr-button",
        "gr-button--icon",
        "gr-button--secondary",
        "gr-icon",
        "gr-menu",
        "gr-menu__item",
        "gr-menu__item--danger",
        "gr-menu__list",
        "gr-menu__separator",
        "gr-menu__shortcut",
        "gr-popover"
      ],
      "tokens": [],
      "icons": [
        "more-horizontal",
        "edit",
        "copy",
        "trash"
      ],
      "states": [
        "closed",
        "open",
        "focused",
        "disabled",
        "danger"
      ],
      "requires": [
        "Grid22",
        "Popover API",
        "Roving menu script"
      ],
      "accessibility": [
        "Use aria-haspopup=menu and keep aria-expanded synchronized.",
        "Move focus into the menu on open and return it to the trigger on close.",
        "Support Arrow Up/Down, Home, End, Escape, and disabled items."
      ],
      "implementation": {
        "html": "<button class=\"gr-button gr-button--secondary gr-button--icon\" type=\"button\" aria-label=\"Open project actions\" aria-haspopup=\"menu\" aria-expanded=\"false\" popovertarget=\"project-actions\">\n  <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <circle cx=\"5\" cy=\"12\" r=\"1\"></circle>\n    <circle cx=\"12\" cy=\"12\" r=\"1\"></circle>\n    <circle cx=\"19\" cy=\"12\" r=\"1\"></circle>\n  </svg>\n</button>\n<div class=\"gr-popover gr-menu\" id=\"project-actions\" popover>\n  <ul class=\"gr-menu__list\" role=\"menu\" aria-label=\"Project actions\">\n    <li role=\"none\">\n      <button class=\"gr-menu__item\" type=\"button\" role=\"menuitem\">\n        <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n          <path d=\"M12 20h9\"></path>\n          <path d=\"M16.5 4.5a2.12 2.12 0 0 1 3 3L7 20l-4 1 1-4z\"></path>\n        </svg>\n        Rename\n        <span class=\"gr-menu__shortcut\" aria-hidden=\"true\">R</span>\n      </button>\n    </li>\n    <li role=\"none\">\n      <button class=\"gr-menu__item\" type=\"button\" role=\"menuitem\">\n        <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n          <rect x=\"10\" y=\"10\" width=\"11\" height=\"11\" rx=\"2\"></rect>\n          <path d=\"M6 16H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path>\n        </svg>\n        Duplicate\n        <span class=\"gr-menu__shortcut\" aria-hidden=\"true\">D</span>\n      </button>\n    </li>\n    <li class=\"gr-menu__separator\" role=\"separator\"></li>\n    <li role=\"none\">\n      <button class=\"gr-menu__item\" type=\"button\" role=\"menuitem\" disabled>Archive</button>\n    </li>\n    <li role=\"none\">\n      <button class=\"gr-menu__item gr-menu__item--danger\" type=\"button\" role=\"menuitem\">\n        <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n          <path d=\"M4 7h16\"></path>\n          <path d=\"M10 11v6\"></path>\n          <path d=\"M14 11v6\"></path>\n          <path d=\"M6 7l0.92 12a2 2 0 0 0 2 1.85h6.16a2 2 0 0 0 2-1.85L18 7\"></path>\n          <path d=\"M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3\"></path>\n        </svg>\n        Delete\n      </button>\n    </li>\n  </ul>\n</div>",
        "javascript": "document.querySelectorAll(\".gr-menu[popover]\").forEach((menu) => {\n  const trigger = document.querySelector(`[popovertarget=\"${CSS.escape(menu.id)}\"]`);\n  if (!trigger) return;\n\n  const items = () => [...menu.querySelectorAll('[role=\"menuitem\"]')]\n    .filter((item) => !item.disabled && item.getAttribute(\"aria-disabled\") !== \"true\");\n\n  function positionMenu() {\n    const rootStyles = getComputedStyle(document.documentElement);\n    const edge = parseFloat(rootStyles.getPropertyValue(\"--gr-space-4\")) || 16;\n    const gap = parseFloat(rootStyles.getPropertyValue(\"--gr-space-1\")) || 4;\n    const viewport = window.visualViewport;\n    const viewportLeft = viewport?.offsetLeft || 0;\n    const viewportTop = viewport?.offsetTop || 0;\n    const viewportWidth = viewport?.width || window.innerWidth;\n    const viewportHeight = viewport?.height || window.innerHeight;\n    const triggerRect = trigger.getBoundingClientRect();\n    const menuRect = menu.getBoundingClientRect();\n    const left = Math.min(Math.max(viewportLeft + edge, triggerRect.right - menuRect.width),\n      Math.max(viewportLeft + edge, viewportLeft + viewportWidth - menuRect.width - edge));\n    const below = triggerRect.bottom + gap;\n    const top = below + menuRect.height <= viewportTop + viewportHeight - edge\n      ? below : Math.max(viewportTop + edge, triggerRect.top - menuRect.height - gap);\n    menu.style.setProperty(\"--_gr-picker-left\", `${left}px`);\n    menu.style.setProperty(\"--_gr-picker-top\", `${top}px`);\n  }\n\n  function focusItem(index) {\n    const available = items();\n    if (available.length) available[(index + available.length) % available.length].focus();\n  }\n\n  function openAt(index) {\n    if (!menu.matches(\":popover-open\")) {\n      menu.showPopover();\n      positionMenu();\n    }\n    requestAnimationFrame(() => {\n      positionMenu();\n      focusItem(index);\n    });\n  }\n\n  trigger.addEventListener(\"click\", () => {\n    requestAnimationFrame(() => {\n      if (menu.matches(\":popover-open\")) {\n        positionMenu();\n        focusItem(0);\n      }\n    });\n  });\n  trigger.addEventListener(\"keydown\", (event) => {\n    if (![\"ArrowDown\", \"ArrowUp\"].includes(event.key)) return;\n    event.preventDefault();\n    openAt(event.key === \"ArrowDown\" ? 0 : -1);\n  });\n  menu.addEventListener(\"toggle\", () => {\n    const open = menu.matches(\":popover-open\");\n    trigger.setAttribute(\"aria-expanded\", String(open));\n    if (open) requestAnimationFrame(positionMenu);\n  });\n  menu.addEventListener(\"keydown\", (event) => {\n    const available = items();\n    const current = available.indexOf(document.activeElement);\n    const positions = {\n      ArrowDown: current + 1,\n      ArrowUp: current - 1,\n      Home: 0,\n      End: -1,\n    };\n    if (positions[event.key] !== undefined) {\n      event.preventDefault();\n      focusItem(positions[event.key]);\n    }\n    if (event.key === \"Escape\") {\n      event.preventDefault();\n      menu.hidePopover();\n      trigger.focus();\n    }\n    if (event.key === \"Tab\") menu.hidePopover();\n  });\n  menu.addEventListener(\"click\", (event) => {\n    const item = event.target.closest('[role=\"menuitem\"]');\n    if (!item || item.disabled || item.getAttribute(\"aria-disabled\") === \"true\") return;\n    menu.hidePopover();\n    trigger.focus();\n  });\n\n  const reposition = () => {\n    if (menu.matches(\":popover-open\")) positionMenu();\n  };\n  window.addEventListener(\"resize\", reposition);\n  window.addEventListener(\"scroll\", reposition, true);\n  window.visualViewport?.addEventListener(\"resize\", reposition);\n  window.visualViewport?.addEventListener(\"scroll\", reposition);\n});",
        "aiPrompt": "Implement the Dropdown Menu component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<button class=\"gr-button gr-button--secondary gr-button--icon\" type=\"button\" aria-label=\"Open project actions\" aria-haspopup=\"menu\" aria-expanded=\"false\" popovertarget=\"project-actions\">\n  <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <circle cx=\"5\" cy=\"12\" r=\"1\"></circle>\n    <circle cx=\"12\" cy=\"12\" r=\"1\"></circle>\n    <circle cx=\"19\" cy=\"12\" r=\"1\"></circle>\n  </svg>\n</button>\n<div class=\"gr-popover gr-menu\" id=\"project-actions\" popover>\n  <ul class=\"gr-menu__list\" role=\"menu\" aria-label=\"Project actions\">\n    <li role=\"none\">\n      <button class=\"gr-menu__item\" type=\"button\" role=\"menuitem\">\n        <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n          <path d=\"M12 20h9\"></path>\n          <path d=\"M16.5 4.5a2.12 2.12 0 0 1 3 3L7 20l-4 1 1-4z\"></path>\n        </svg>\n        Rename\n        <span class=\"gr-menu__shortcut\" aria-hidden=\"true\">R</span>\n      </button>\n    </li>\n    <li role=\"none\">\n      <button class=\"gr-menu__item\" type=\"button\" role=\"menuitem\">\n        <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n          <rect x=\"10\" y=\"10\" width=\"11\" height=\"11\" rx=\"2\"></rect>\n          <path d=\"M6 16H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path>\n        </svg>\n        Duplicate\n        <span class=\"gr-menu__shortcut\" aria-hidden=\"true\">D</span>\n      </button>\n    </li>\n    <li class=\"gr-menu__separator\" role=\"separator\"></li>\n    <li role=\"none\">\n      <button class=\"gr-menu__item\" type=\"button\" role=\"menuitem\" disabled>Archive</button>\n    </li>\n    <li role=\"none\">\n      <button class=\"gr-menu__item gr-menu__item--danger\" type=\"button\" role=\"menuitem\">\n        <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n          <path d=\"M4 7h16\"></path>\n          <path d=\"M10 11v6\"></path>\n          <path d=\"M14 11v6\"></path>\n          <path d=\"M6 7l0.92 12a2 2 0 0 0 2 1.85h6.16a2 2 0 0 0 2-1.85L18 7\"></path>\n          <path d=\"M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3\"></path>\n        </svg>\n        Delete\n      </button>\n    </li>\n  </ul>\n</div>\n```\n\nUse this JavaScript behavior:\n\n```js\ndocument.querySelectorAll(\".gr-menu[popover]\").forEach((menu) => {\n  const trigger = document.querySelector(`[popovertarget=\"${CSS.escape(menu.id)}\"]`);\n  if (!trigger) return;\n\n  const items = () => [...menu.querySelectorAll('[role=\"menuitem\"]')]\n    .filter((item) => !item.disabled && item.getAttribute(\"aria-disabled\") !== \"true\");\n\n  function positionMenu() {\n    const rootStyles = getComputedStyle(document.documentElement);\n    const edge = parseFloat(rootStyles.getPropertyValue(\"--gr-space-4\")) || 16;\n    const gap = parseFloat(rootStyles.getPropertyValue(\"--gr-space-1\")) || 4;\n    const viewport = window.visualViewport;\n    const viewportLeft = viewport?.offsetLeft || 0;\n    const viewportTop = viewport?.offsetTop || 0;\n    const viewportWidth = viewport?.width || window.innerWidth;\n    const viewportHeight = viewport?.height || window.innerHeight;\n    const triggerRect = trigger.getBoundingClientRect();\n    const menuRect = menu.getBoundingClientRect();\n    const left = Math.min(Math.max(viewportLeft + edge, triggerRect.right - menuRect.width),\n      Math.max(viewportLeft + edge, viewportLeft + viewportWidth - menuRect.width - edge));\n    const below = triggerRect.bottom + gap;\n    const top = below + menuRect.height <= viewportTop + viewportHeight - edge\n      ? below : Math.max(viewportTop + edge, triggerRect.top - menuRect.height - gap);\n    menu.style.setProperty(\"--_gr-picker-left\", `${left}px`);\n    menu.style.setProperty(\"--_gr-picker-top\", `${top}px`);\n  }\n\n  function focusItem(index) {\n    const available = items();\n    if (available.length) available[(index + available.length) % available.length].focus();\n  }\n\n  function openAt(index) {\n    if (!menu.matches(\":popover-open\")) {\n      menu.showPopover();\n      positionMenu();\n    }\n    requestAnimationFrame(() => {\n      positionMenu();\n      focusItem(index);\n    });\n  }\n\n  trigger.addEventListener(\"click\", () => {\n    requestAnimationFrame(() => {\n      if (menu.matches(\":popover-open\")) {\n        positionMenu();\n        focusItem(0);\n      }\n    });\n  });\n  trigger.addEventListener(\"keydown\", (event) => {\n    if (![\"ArrowDown\", \"ArrowUp\"].includes(event.key)) return;\n    event.preventDefault();\n    openAt(event.key === \"ArrowDown\" ? 0 : -1);\n  });\n  menu.addEventListener(\"toggle\", () => {\n    const open = menu.matches(\":popover-open\");\n    trigger.setAttribute(\"aria-expanded\", String(open));\n    if (open) requestAnimationFrame(positionMenu);\n  });\n  menu.addEventListener(\"keydown\", (event) => {\n    const available = items();\n    const current = available.indexOf(document.activeElement);\n    const positions = {\n      ArrowDown: current + 1,\n      ArrowUp: current - 1,\n      Home: 0,\n      End: -1,\n    };\n    if (positions[event.key] !== undefined) {\n      event.preventDefault();\n      focusItem(positions[event.key]);\n    }\n    if (event.key === \"Escape\") {\n      event.preventDefault();\n      menu.hidePopover();\n      trigger.focus();\n    }\n    if (event.key === \"Tab\") menu.hidePopover();\n  });\n  menu.addEventListener(\"click\", (event) => {\n    const item = event.target.closest('[role=\"menuitem\"]');\n    if (!item || item.disabled || item.getAttribute(\"aria-disabled\") === \"true\") return;\n    menu.hidePopover();\n    trigger.focus();\n  });\n\n  const reposition = () => {\n    if (menu.matches(\":popover-open\")) positionMenu();\n  };\n  window.addEventListener(\"resize\", reposition);\n  window.addEventListener(\"scroll\", reposition, true);\n  window.visualViewport?.addEventListener(\"resize\", reposition);\n  window.visualViewport?.addEventListener(\"scroll\", reposition);\n});\n```\n\nAccessibility requirements:\n- Use aria-haspopup=menu and keep aria-expanded synchronized.\n- Move focus into the menu on open and return it to the trigger on close.\n- Support Arrow Up/Down, Home, End, Escape, and disabled items.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "callout",
      "name": "Callout",
      "category": "Feedback",
      "description": "A message that stays on the page.",
      "docs": "https://www.greda.design/#callout",
      "search": [
        "callout",
        "Callout",
        "A message that stays on the page.",
        "notice",
        "inline alert"
      ],
      "classes": [
        "gr-callout",
        "gr-callout--danger",
        "gr-callout__body",
        "gr-callout__title",
        "gr-code",
        "gr-icon"
      ],
      "tokens": [],
      "icons": [
        "info",
        "alert-triangle"
      ],
      "states": [],
      "requires": [],
      "accessibility": [
        "Preserve semantic HTML.",
        "Keep visible focus on interactive elements."
      ],
      "implementation": {
        "html": "<div class=\"gr-callout\">\n  <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <circle cx=\"12\" cy=\"12\" r=\"9\"></circle>\n    <path d=\"M12 11v5\"></path>\n    <path d=\"M12 8h.01\"></path>\n  </svg>\n  <div>\n    <p class=\"gr-callout__title\">Breakpoints are a fixed scale</p>\n    <p class=\"gr-callout__body\">A media query cannot read a token, so <code class=\"gr-code\">sm</code>, <code class=\"gr-code\">md</code> and <code class=\"gr-code\">lg</code> are written out.</p>\n  </div>\n</div>\n<div class=\"gr-callout gr-callout--danger\">\n  <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <path d=\"M13.66 5.967L20.31 15.797A2 2 0 0 1 18.65 18.917L5.35 18.917A2 2 0 0 1 3.69 15.797L10.34 5.967A2 2 0 0 1 13.66 5.967Z\"></path>\n    <path d=\"M12 9.617v3.9\"></path>\n    <path d=\"M12 16.217h0.01\"></path>\n  </svg>\n  <div>\n    <p class=\"gr-callout__title\">The accent does not separate focus from error</p>\n    <p class=\"gr-callout__body\">Today <code class=\"gr-code\">danger</code> resolves to the accent; separating them is one line in <code class=\"gr-code\">tokens.css</code>.</p>\n  </div>\n</div>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Callout component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-callout\">\n  <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <circle cx=\"12\" cy=\"12\" r=\"9\"></circle>\n    <path d=\"M12 11v5\"></path>\n    <path d=\"M12 8h.01\"></path>\n  </svg>\n  <div>\n    <p class=\"gr-callout__title\">Breakpoints are a fixed scale</p>\n    <p class=\"gr-callout__body\">A media query cannot read a token, so <code class=\"gr-code\">sm</code>, <code class=\"gr-code\">md</code> and <code class=\"gr-code\">lg</code> are written out.</p>\n  </div>\n</div>\n<div class=\"gr-callout gr-callout--danger\">\n  <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <path d=\"M13.66 5.967L20.31 15.797A2 2 0 0 1 18.65 18.917L5.35 18.917A2 2 0 0 1 3.69 15.797L10.34 5.967A2 2 0 0 1 13.66 5.967Z\"></path>\n    <path d=\"M12 9.617v3.9\"></path>\n    <path d=\"M12 16.217h0.01\"></path>\n  </svg>\n  <div>\n    <p class=\"gr-callout__title\">The accent does not separate focus from error</p>\n    <p class=\"gr-callout__body\">Today <code class=\"gr-code\">danger</code> resolves to the accent; separating them is one line in <code class=\"gr-code\">tokens.css</code>.</p>\n  </div>\n</div>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Preserve semantic HTML.\n- Keep visible focus on interactive elements.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "toast",
      "name": "Toast",
      "category": "Feedback",
      "description": "Transient; it lives on the toast layer.",
      "docs": "https://www.greda.design/#toast",
      "search": [
        "toast",
        "Toast",
        "Transient; it lives on the toast layer.",
        "notification",
        "status"
      ],
      "classes": [
        "gr-code",
        "gr-field__message",
        "gr-icon",
        "gr-toast",
        "gr-toast__message"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "visible"
      ],
      "requires": [],
      "accessibility": [
        "Use role=status for non-urgent updates and keep the message as text."
      ],
      "implementation": {
        "html": "<div class=\"gr-toast\" role=\"status\">\n  <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <path d=\"M20 6L9 17l-5-5\"></path>\n  </svg>\n  <span class=\"gr-toast__message\">Tokens validated: 2 documents, 7 stylesheets.</span>\n</div>\n<p class=\"gr-field__message\">In real use it sits inside <code class=\"gr-code\">.gr-toast-region</code>, which pins it to the bottom and intercepts no clicks.</p>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Toast component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-toast\" role=\"status\">\n  <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <path d=\"M20 6L9 17l-5-5\"></path>\n  </svg>\n  <span class=\"gr-toast__message\">Tokens validated: 2 documents, 7 stylesheets.</span>\n</div>\n<p class=\"gr-field__message\">In real use it sits inside <code class=\"gr-code\">.gr-toast-region</code>, which pins it to the bottom and intercepts no clicks.</p>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use role=status for non-urgent updates and keep the message as text.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "spinner-progress",
      "name": "Spinner and Progress",
      "category": "Feedback",
      "description": "Indeterminate and determinate; the second is <progress> .",
      "docs": "https://www.greda.design/#spinner-progress",
      "search": [
        "spinner-progress",
        "Spinner and Progress",
        "Indeterminate and determinate; the second is <progress> .",
        "loading",
        "progress"
      ],
      "classes": [
        "gr-code",
        "gr-field__message",
        "gr-loading",
        "gr-progress",
        "gr-spinner"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "indeterminate",
        "determinate"
      ],
      "requires": [],
      "accessibility": [
        "Never use motion as the only loading message.",
        "Use native progress when a value exists."
      ],
      "implementation": {
        "html": "<span class=\"gr-loading\">\n  <span class=\"gr-spinner\" aria-hidden=\"true\"></span>\n  Validating tokens…\n</span>\n<progress class=\"gr-progress\" value=\"0.62\" aria-label=\"Validation progress\"></progress>\n<p class=\"gr-field__message\">The spinner never travels alone: <code class=\"gr-code\">prefers-reduced-motion</code> freezes it, and a still circle says nothing. The label does.</p>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Spinner and Progress component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<span class=\"gr-loading\">\n  <span class=\"gr-spinner\" aria-hidden=\"true\"></span>\n  Validating tokens…\n</span>\n<progress class=\"gr-progress\" value=\"0.62\" aria-label=\"Validation progress\"></progress>\n<p class=\"gr-field__message\">The spinner never travels alone: <code class=\"gr-code\">prefers-reduced-motion</code> freezes it, and a still circle says nothing. The label does.</p>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Never use motion as the only loading message.\n- Use native progress when a value exists.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "skeleton",
      "name": "Skeleton",
      "category": "Feedback",
      "description": "Only opacity moves; the block keeps its size.",
      "docs": "https://www.greda.design/#skeleton",
      "search": [
        "skeleton",
        "Skeleton",
        "Only opacity moves; the block keeps its size.",
        "loading placeholder"
      ],
      "classes": [
        "gr-card",
        "gr-skeleton",
        "gr-skeleton--text",
        "gr-skeleton--title",
        "gr-stack"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "loading"
      ],
      "requires": [],
      "accessibility": [
        "Keep its final dimensions to avoid layout shift.",
        "Respect reduced motion."
      ],
      "implementation": {
        "html": "<div class=\"gr-card demo-card\">\n  <div class=\"gr-stack\">\n    <div class=\"gr-skeleton gr-skeleton--title\"></div>\n    <div class=\"gr-skeleton gr-skeleton--text\"></div>\n    <div class=\"gr-skeleton gr-skeleton--text\"></div>\n  </div>\n</div>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Skeleton component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-card demo-card\">\n  <div class=\"gr-stack\">\n    <div class=\"gr-skeleton gr-skeleton--title\"></div>\n    <div class=\"gr-skeleton gr-skeleton--text\"></div>\n    <div class=\"gr-skeleton gr-skeleton--text\"></div>\n  </div>\n</div>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Keep its final dimensions to avoid layout shift.\n- Respect reduced motion.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "empty-state",
      "name": "Empty state",
      "category": "Feedback",
      "description": "Name what is missing and offer the way out.",
      "docs": "https://www.greda.design/#empty-state",
      "search": [
        "empty-state",
        "Empty state",
        "Name what is missing and offer the way out.",
        "no results",
        "blank slate"
      ],
      "classes": [
        "gr-button",
        "gr-button--secondary",
        "gr-empty",
        "gr-empty__body",
        "gr-empty__title",
        "gr-icon"
      ],
      "tokens": [],
      "icons": [
        "search"
      ],
      "states": [],
      "requires": [],
      "accessibility": [
        "Name what is missing and provide a next action."
      ],
      "implementation": {
        "html": "<div class=\"gr-empty\">\n  <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <circle cx=\"9.82\" cy=\"9.82\" r=\"6.67\"></circle>\n    <path d=\"M15.05 15.05L20.85 20.85\"></path>\n  </svg>\n  <p class=\"gr-empty__title\">No component matches</p>\n  <p class=\"gr-empty__body\">Try another name, or look at the full list of primitives.</p>\n  <a class=\"gr-button gr-button--secondary\" href=\"#components\">View all</a>\n</div>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Empty state component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<div class=\"gr-empty\">\n  <svg class=\"gr-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <circle cx=\"9.82\" cy=\"9.82\" r=\"6.67\"></circle>\n    <path d=\"M15.05 15.05L20.85 20.85\"></path>\n  </svg>\n  <p class=\"gr-empty__title\">No component matches</p>\n  <p class=\"gr-empty__body\">Try another name, or look at the full list of primitives.</p>\n  <a class=\"gr-button gr-button--secondary\" href=\"#components\">View all</a>\n</div>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Name what is missing and provide a next action.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "breadcrumb",
      "name": "Breadcrumb",
      "category": "Navigation",
      "description": "The current page is marked with aria-current .",
      "docs": "https://www.greda.design/#breadcrumb",
      "search": [
        "breadcrumb",
        "Breadcrumb",
        "The current page is marked with aria-current .",
        "path",
        "hierarchy"
      ],
      "classes": [
        "gr-breadcrumb",
        "gr-breadcrumb__item",
        "gr-breadcrumb__list",
        "gr-link",
        "gr-link--quiet"
      ],
      "tokens": [],
      "icons": [],
      "states": [],
      "requires": [],
      "accessibility": [
        "Mark the current item with aria-current."
      ],
      "implementation": {
        "html": "<nav class=\"gr-breadcrumb\" aria-label=\"Breadcrumb\">\n  <ol class=\"gr-breadcrumb__list\">\n    <li class=\"gr-breadcrumb__item\"><a class=\"gr-link gr-link--quiet\" href=\"#top\">Greda</a></li>\n    <li class=\"gr-breadcrumb__item\"><a class=\"gr-link gr-link--quiet\" href=\"#components\">Components</a></li>\n    <li class=\"gr-breadcrumb__item\" aria-current=\"page\">Breadcrumb</li>\n  </ol>\n</nav>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Breadcrumb component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<nav class=\"gr-breadcrumb\" aria-label=\"Breadcrumb\">\n  <ol class=\"gr-breadcrumb__list\">\n    <li class=\"gr-breadcrumb__item\"><a class=\"gr-link gr-link--quiet\" href=\"#top\">Greda</a></li>\n    <li class=\"gr-breadcrumb__item\"><a class=\"gr-link gr-link--quiet\" href=\"#components\">Components</a></li>\n    <li class=\"gr-breadcrumb__item\" aria-current=\"page\">Breadcrumb</li>\n  </ol>\n</nav>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Mark the current item with aria-current.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "pagination",
      "name": "Pagination",
      "category": "Navigation",
      "description": "44px targets, 32px with a precise pointer.",
      "docs": "https://www.greda.design/#pagination",
      "search": [
        "pagination",
        "Pagination",
        "44px targets, 32px with a precise pointer.",
        "pages",
        "previous next"
      ],
      "classes": [
        "gr-pagination",
        "gr-pagination__link",
        "gr-pagination__list"
      ],
      "tokens": [],
      "icons": [],
      "states": [
        "current",
        "disabled"
      ],
      "requires": [],
      "accessibility": [
        "Give the navigation an accessible name and mark the current page."
      ],
      "implementation": {
        "html": "<nav class=\"gr-pagination\" aria-label=\"Pagination\">\n  <ul class=\"gr-pagination__list\">\n    <li><a class=\"gr-pagination__link\" href=\"#components\" aria-disabled=\"true\">Previous</a></li>\n    <li><a class=\"gr-pagination__link\" href=\"#components\" aria-current=\"page\">1</a></li>\n    <li><a class=\"gr-pagination__link\" href=\"#components\">2</a></li>\n    <li><a class=\"gr-pagination__link\" href=\"#components\">3</a></li>\n    <li><a class=\"gr-pagination__link\" href=\"#components\">Next</a></li>\n  </ul>\n</nav>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Pagination component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<nav class=\"gr-pagination\" aria-label=\"Pagination\">\n  <ul class=\"gr-pagination__list\">\n    <li><a class=\"gr-pagination__link\" href=\"#components\" aria-disabled=\"true\">Previous</a></li>\n    <li><a class=\"gr-pagination__link\" href=\"#components\" aria-current=\"page\">1</a></li>\n    <li><a class=\"gr-pagination__link\" href=\"#components\">2</a></li>\n    <li><a class=\"gr-pagination__link\" href=\"#components\">3</a></li>\n    <li><a class=\"gr-pagination__link\" href=\"#components\">Next</a></li>\n  </ul>\n</nav>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Give the navigation an accessible name and mark the current page.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "site-chrome",
      "name": "Header, Nav and Footer",
      "category": "Navigation",
      "description": "This page's own; they no longer live in docs.css .",
      "docs": "https://www.greda.design/#site-chrome",
      "search": [
        "site-chrome",
        "Header, Nav and Footer",
        "This page's own; they no longer live in docs.css .",
        "header",
        "navigation",
        "footer"
      ],
      "classes": [
        "gr-code",
        "gr-field__message"
      ],
      "tokens": [],
      "icons": [],
      "states": [],
      "requires": [],
      "accessibility": [
        "Use semantic header, nav, main, and footer landmarks."
      ],
      "implementation": {
        "html": "<p class=\"gr-field__message\">The top bar is <code class=\"gr-code\">.gr-header</code> with <code class=\"gr-code\">.gr-nav</code>, and it uses the <code class=\"gr-code\">sticky</code> layer instead of an invented z-index. The footer is <code class=\"gr-code\">.gr-footer</code>.</p>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Header, Nav and Footer component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<p class=\"gr-field__message\">The top bar is <code class=\"gr-code\">.gr-header</code> with <code class=\"gr-code\">.gr-nav</code>, and it uses the <code class=\"gr-code\">sticky</code> layer instead of an invented z-index. The footer is <code class=\"gr-code\">.gr-footer</code>.</p>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Use semantic header, nav, main, and footer landmarks.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    },
    {
      "id": "skip-link",
      "name": "Skip link",
      "category": "Navigation",
      "description": "The page's first focus; revealed by keyboard.",
      "docs": "https://www.greda.design/#skip-link",
      "search": [
        "skip-link",
        "Skip link",
        "The page's first focus; revealed by keyboard.",
        "skip navigation",
        "accessibility"
      ],
      "classes": [
        "gr-button",
        "gr-button--primary",
        "gr-field__message"
      ],
      "tokens": [],
      "icons": [],
      "states": [],
      "requires": [],
      "accessibility": [
        "Place it first in the body and reveal it on keyboard focus."
      ],
      "implementation": {
        "html": "<a class=\"gr-button gr-button--primary\" href=\"#top\">Skip to content</a>\n<p class=\"gr-field__message\">It lives at the start of the <code>&lt;body&gt;</code>. Press <kbd>Tab</kbd> on load to watch it appear.</p>",
        "javascript": "// No JavaScript required.",
        "aiPrompt": "Implement the Skip link component with Greda Design System v0.7.0.\n\nLoad https://www.greda.design/v0.7.0/greda.css and use only documented gr-* classes and --gr-* tokens.\n\nStart from this exact HTML:\n\n```html\n<a class=\"gr-button gr-button--primary\" href=\"#top\">Skip to content</a>\n<p class=\"gr-field__message\">It lives at the start of the <code>&lt;body&gt;</code>. Press <kbd>Tab</kbd> on load to watch it appear.</p>\n```\n\nNo JavaScript is required for this example.\n\nAccessibility requirements:\n- Place it first in the body and reveal it on keyboard focus.\n\nUse exact Grid22 icons when an icon is present. Preserve native HTML semantics. Verify the result from 360px upward, in light and dark themes, and with keyboard-only navigation."
      }
    }
  ]
}
