{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "number-input",
  "type": "registry:ui",
  "title": "Number Input",
  "description": "Native <input type=\"number\"> styled like Input, with optional −/+ stepper buttons that call stepUp()/stepDown(). role=spinbutton, aria-valuenow/min/max, arrow-key stepping and value clamping all come from the platform. Ships in five flavours.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/number-input.tsx",
      "type": "registry:ui",
      "target": "components/ui/number-input.tsx",
      "content": "/** @jsxImportSource hono/jsx */\nimport { cn, type ClassValue } from \"@/registry/lib/cn\"\n\n// Number Input — shadcn-htmx, htmx v4 + Tailwind v4.\n//\n// shadcn/ui has no dedicated \"number input\" — it composes one from <Input\n// type=\"number\"> + Button (see repos/shadcn-ui/apps/v4/registry/new-york-v4/ui/input.tsx\n// for the Input we mirror). We do the same, but lean entirely on the platform.\n//\n// The control IS a native <input type=\"number\">. Per MDN that element ships:\n//   - implicit role=\"spinbutton\"\n//   - aria-valuenow / aria-valuemin / aria-valuemax auto-managed from\n//     value / min / max — no manual ARIA\n//   - the full APG spinbutton keyboard contract built in: ArrowUp increases,\n//     ArrowDown decreases, plus standard single-line text editing\n//   - stepUp() / stepDown() DOM methods used by the optional +/- buttons\n//   See repos/mdn/files/en-us/web/html/reference/elements/input/number/index.md:298,468\n//   APG: repos/aria-practices/content/patterns/spinbutton/spinbutton-pattern.html\n//\n// Because the native input already satisfies the spinbutton pattern, the only\n// JS is a 3-line stepUp/stepDown handler for the optional buttons (public/site.js,\n// keyed on data-slot=\"number-input\"). With `steppers={false}` it is zero-JS.\n//\n// We hide the browser's default up/down spinners (they are tiny and inconsistent\n// across engines) and supply our own larger, accessible buttons instead — the\n// APG quantity-spinbutton example takes the same approach\n// (repos/aria-practices/content/patterns/spinbutton/examples/quantity-spinbutton.html).\n\n// Shared <Input> base — kept byte-for-byte in sync with registry/ui/input.tsx\n// so a number input looks identical to every other field. We add rules to hide\n// the native spinner buttons and reserve right padding when our own steppers\n// sit inside the field.\nconst inputBase =\n  \"flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none \" +\n  \"selection:bg-primary selection:text-primary-foreground \" +\n  \"placeholder:text-muted-foreground \" +\n  \"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 \" +\n  \"md:text-sm dark:bg-input/30 \" +\n  \"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 \" +\n  \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 \" +\n  \"[&.htmx-request]:opacity-70 \" +\n  // Hide the native up/down spinner — Chromium (-webkit) + Firefox (-moz).\n  \"[&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]\"\n\n// Stepper button: square, sized to the 9-unit field height, ghost styling that\n// matches the muted/accent palette other controls use.\nconst stepperBtn =\n  \"inline-flex size-9 shrink-0 items-center justify-center text-muted-foreground transition-colors outline-none select-none \" +\n  \"hover:text-foreground hover:bg-accent \" +\n  \"focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:relative focus-visible:z-10 \" +\n  \"disabled:pointer-events-none disabled:opacity-50\"\n\nexport function numberInputClasses(opts?: { class?: ClassValue }): string {\n  return cn(inputBase, opts?.class)\n}\n\ntype NumberInputProps = {\n  id?: string\n  name?: string\n  value?: number | string\n  placeholder?: string\n  min?: number | string\n  max?: number | string\n  step?: number | string\n  required?: boolean\n  disabled?: boolean\n  readonly?: boolean\n  autofocus?: boolean\n  form?: string\n  list?: string\n  // Browser autofill hint — supported common attribute on <input type=number>\n  // (e.g. \"postal-code\"). repos/mdn/.../elements/input/number/index.md:300,449\n  autocomplete?: string\n\n  // Render the styled −/+ stepper buttons around the field. Defaults to true.\n  // When false the component is a bare native spinbutton (zero JS).\n  steppers?: boolean\n\n  // Mobile keyboard hint. \"decimal\" for prices, \"numeric\" for integers.\n  inputmode?: \"none\" | \"numeric\" | \"decimal\"\n\n  // ARIA / labelling. The native input is already role=spinbutton with\n  // aria-valuenow/min/max derived from value/min/max.\n  ariaLabel?: string\n  ariaLabelledby?: string\n  ariaDescribedby?: string\n  ariaInvalid?: boolean | \"grammar\" | \"spelling\"\n  ariaRequired?: boolean\n  // Human-readable value for screen readers when the raw number isn't friendly\n  // (currency/units). APG spinbutton: aria-practices/.../spinbutton-pattern.html:92\n  ariaValuetext?: string\n\n  class?: ClassValue\n\n  // htmx v4 passthrough — fires on the input's change event by default. Use\n  // hx-trigger to override. See repos/htmx/www/reference.md.\n  [key: `hx-${string}`]: any\n  [key: `data-${string}`]: any\n}\n\nexport function NumberInput(props: NumberInputProps) {\n  const {\n    id,\n    name,\n    value,\n    placeholder,\n    min,\n    max,\n    step,\n    required,\n    disabled,\n    readonly,\n    autofocus,\n    form,\n    list,\n    autocomplete,\n    steppers = true,\n    inputmode,\n    ariaLabel,\n    ariaLabelledby,\n    ariaDescribedby,\n    ariaInvalid,\n    ariaRequired,\n    ariaValuetext,\n    class: className,\n    ...rest\n  } = props\n\n  const field = (\n    <input\n      type=\"number\"\n      id={id}\n      name={name}\n      value={value}\n      placeholder={placeholder}\n      min={min}\n      max={max}\n      step={step}\n      required={required}\n      disabled={disabled}\n      readonly={readonly}\n      autofocus={autofocus}\n      form={form}\n      list={list}\n      autocomplete={autocomplete}\n      inputmode={inputmode}\n      aria-label={ariaLabel}\n      aria-labelledby={ariaLabelledby}\n      aria-describedby={ariaDescribedby}\n      aria-valuetext={ariaValuetext}\n      aria-invalid={ariaInvalid === undefined ? undefined : String(ariaInvalid)}\n      aria-required={ariaRequired === undefined ? undefined : String(ariaRequired)}\n      data-slot={steppers ? \"number-input-field\" : \"number-input\"}\n      class={cn(inputBase, steppers && \"rounded-none border-0 text-center shadow-none focus-visible:ring-0\", className)}\n      {...(steppers ? {} : rest)}\n    />\n  )\n\n  if (!steppers) return field\n\n  // Steppers on: a flex shell carries the border + focus ring (focus-within),\n  // the bare buttons call stepDown()/stepUp() via public/site.js, and the\n  // input keeps its native role=spinbutton + arrow-key contract.\n  return (\n    <div\n      data-slot=\"number-input\"\n      data-disabled={disabled ? \"true\" : undefined}\n      class={cn(\n        \"flex h-9 w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] dark:bg-input/30\",\n        \"focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50\",\n        \"has-[input[aria-invalid=true]]:border-destructive has-[input[aria-invalid=true]]:ring-destructive/20 dark:has-[input[aria-invalid=true]]:ring-destructive/40\",\n        \"has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50\",\n        className,\n      )}\n      {...rest}\n    >\n      <button\n        type=\"button\"\n        data-step=\"down\"\n        tabindex={-1}\n        disabled={disabled}\n        aria-label=\"Decrease\"\n        title=\"Decrease\"\n        class={cn(stepperBtn, \"rounded-l-md border-r border-input\")}\n      >\n        <span aria-hidden=\"true\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\">\n            <path d=\"M5 12h14\" />\n          </svg>\n        </span>\n      </button>\n      {field}\n      <button\n        type=\"button\"\n        data-step=\"up\"\n        tabindex={-1}\n        disabled={disabled}\n        aria-label=\"Increase\"\n        title=\"Increase\"\n        class={cn(stepperBtn, \"rounded-r-md border-l border-input\")}\n      >\n        <span aria-hidden=\"true\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\">\n            <path d=\"M5 12h14\" />\n            <path d=\"M12 5v14\" />\n          </svg>\n        </span>\n      </button>\n    </div>\n  )\n}\n"
    },
    {
      "path": "registry/jinja2/number-input.html",
      "type": "registry:file",
      "target": "templates/components/number-input.html",
      "content": "{# Number Input macro — shadcn-htmx, htmx v4 + Tailwind v4.\n   Mirrors registry/ui/number-input.tsx for Python/Flask/FastAPI/Django/Jinja2.\n\n   The control is a native <input type=\"number\"> — role=spinbutton,\n   aria-valuenow/min/max, and the ArrowUp/ArrowDown stepping contract all come\n   from the platform. The optional −/+ buttons call stepDown()/stepUp() via the\n   shared handler in public/site.js (keyed on data-slot=\"number-input\").\n\n   Usage:\n       {% from \"components/number-input.html\" import number_input %}\n       {{ number_input(name=\"qty\", value=1, min=0, max=10) }}\n       {{ number_input(name=\"price\", min=0, step=\"0.01\", inputmode=\"decimal\", steppers=false) }}\n\n   See repos/mdn/files/en-us/web/html/reference/elements/input/number/index.md. #}\n\n{% macro number_input(\n    id=none,\n    name=none,\n    value=none,\n    placeholder=none,\n    min=none,\n    max=none,\n    step=none,\n    required=false,\n    disabled=false,\n    readonly=false,\n    autofocus=false,\n    form=none,\n    list=none,\n    autocomplete=none,\n    steppers=true,\n    inputmode=none,\n    aria_label=none,\n    aria_labelledby=none,\n    aria_describedby=none,\n    aria_invalid=none,\n    aria_required=none,\n    aria_valuetext=none,\n    extra_class=\"\",\n    **attrs\n) %}\n{%- set input_base -%}\nflex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&.htmx-request]:opacity-70 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]\n{%- endset -%}\n{%- set stepper_btn -%}\ninline-flex size-9 shrink-0 items-center justify-center text-muted-foreground transition-colors outline-none select-none hover:text-foreground hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:relative focus-visible:z-10 disabled:pointer-events-none disabled:opacity-50\n{%- endset -%}\n{%- macro field(slot) -%}\n<input type=\"number\"\n       {%- if id %} id=\"{{ id }}\"{% endif %}\n       {%- if name %} name=\"{{ name }}\"{% endif %}\n       {%- if value is not none %} value=\"{{ value }}\"{% endif %}\n       {%- if placeholder %} placeholder=\"{{ placeholder }}\"{% endif %}\n       {%- if min is not none %} min=\"{{ min }}\"{% endif %}\n       {%- if max is not none %} max=\"{{ max }}\"{% endif %}\n       {%- if step is not none %} step=\"{{ step }}\"{% endif %}\n       {%- if required %} required{% endif %}\n       {%- if disabled %} disabled{% endif %}\n       {%- if readonly %} readonly{% endif %}\n       {%- if autofocus %} autofocus{% endif %}\n       {%- if form %} form=\"{{ form }}\"{% endif %}\n       {%- if list %} list=\"{{ list }}\"{% endif %}\n       {# autocomplete: supported common attr on <input type=number> — MDN input/number #}\n       {%- if autocomplete %} autocomplete=\"{{ autocomplete }}\"{% endif %}\n       {%- if inputmode %} inputmode=\"{{ inputmode }}\"{% endif %}\n       {%- if aria_label %} aria-label=\"{{ aria_label }}\"{% endif %}\n       {%- if aria_labelledby %} aria-labelledby=\"{{ aria_labelledby }}\"{% endif %}\n       {%- if aria_describedby %} aria-describedby=\"{{ aria_describedby }}\"{% endif %}\n       {# aria-valuetext: human-readable spinbutton value — APG spinbutton pattern #}\n       {%- if aria_valuetext %} aria-valuetext=\"{{ aria_valuetext }}\"{% endif %}\n       {%- if aria_invalid is not none %} aria-invalid=\"{{ aria_invalid|string|lower }}\"{% endif %}\n       {%- if aria_required is not none %} aria-required=\"{{ aria_required|string|lower }}\"{% endif %}\n       data-slot=\"{{ slot }}\"\n       class=\"{{ input_base }}{% if steppers %} rounded-none border-0 text-center shadow-none focus-visible:ring-0{% endif %} {{ extra_class }}\"\n       {%- if not steppers %}{% for k, v in attrs.items() %} {{ k|replace('_', '-') }}=\"{{ v }}\"{% endfor %}{% endif -%}\n>\n{%- endmacro -%}\n{%- if steppers -%}\n<div data-slot=\"number-input\"\n     {%- if disabled %} data-disabled=\"true\"{% endif %}\n     class=\"flex h-9 w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] dark:bg-input/30 focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 has-[input[aria-invalid=true]]:border-destructive has-[input[aria-invalid=true]]:ring-destructive/20 dark:has-[input[aria-invalid=true]]:ring-destructive/40 has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50\"\n     {%- for k, v in attrs.items() %} {{ k|replace('_', '-') }}=\"{{ v }}\"{% endfor -%}\n>\n  <button type=\"button\" data-step=\"down\" tabindex=\"-1\"{% if disabled %} disabled{% endif %} aria-label=\"Decrease\" title=\"Decrease\"\n          class=\"{{ stepper_btn }} rounded-l-md border-r border-input\">\n    <span aria-hidden=\"true\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\"><path d=\"M5 12h14\"/></svg>\n    </span>\n  </button>\n  {{ field(\"number-input-field\") }}\n  <button type=\"button\" data-step=\"up\" tabindex=\"-1\"{% if disabled %} disabled{% endif %} aria-label=\"Increase\" title=\"Increase\"\n          class=\"{{ stepper_btn }} rounded-r-md border-l border-input\">\n    <span aria-hidden=\"true\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\"><path d=\"M5 12h14\"/><path d=\"M12 5v14\"/></svg>\n    </span>\n  </button>\n</div>\n{%- else -%}\n{{ field(\"number-input\") }}\n{%- endif -%}\n{% endmacro %}\n"
    },
    {
      "path": "registry/go-templates/number-input.tmpl",
      "type": "registry:file",
      "target": "components/number-input.tmpl",
      "content": "{{/*\n  Number Input template — shadcn-htmx, htmx v4 + Tailwind v4.\n  Mirrors registry/ui/number-input.tsx for Go projects using html/template.\n\n  The control is a native <input type=\"number\">: role=spinbutton,\n  aria-valuenow/min/max, and the ArrowUp/ArrowDown stepping contract all come\n  from the platform. The optional −/+ buttons call stepDown()/stepUp() via the\n  shared handler in public/site.js (keyed on data-slot=\"number-input\").\n\n  Args (via dict):\n      ID, Name, Value, Placeholder string\n      Min, Max, Step               string  // numeric attrs, as strings\n      Required, Disabled, Readonly bool\n      Autofocus                    bool\n      Form, List                   string\n      Autocomplete                 string  // browser autofill hint, e.g. \"postal-code\"\n      NoSteppers                   bool    // omit the −/+ buttons (bare field)\n      InputMode                    string  // \"\" | \"numeric\" | \"decimal\"\n      AriaLabel, AriaLabelledby    string\n      AriaDescribedby              string\n      AriaValuetext                string  // human-readable value for screen readers\n      AriaInvalid, AriaRequired    string  // \"true\" | \"false\"\n      Attrs                        map[string]string // hx-*, data-*, …\n\n      {{template \"number-input\" (dict \"Name\" \"qty\" \"Value\" \"1\" \"Min\" \"0\" \"Max\" \"10\")}}\n\n  See repos/mdn/files/en-us/web/html/reference/elements/input/number/index.md.\n*/}}\n\n{{define \"number-input\"}}\n{{- $steppers := not .NoSteppers -}}\n{{- $inputBase := \"flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&.htmx-request]:opacity-70 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]\" -}}\n{{- $stepperBtn := \"inline-flex size-9 shrink-0 items-center justify-center text-muted-foreground transition-colors outline-none select-none hover:text-foreground hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:relative focus-visible:z-10 disabled:pointer-events-none disabled:opacity-50\" -}}\n{{- if $steppers -}}\n<div data-slot=\"number-input\"\n     {{- if .Disabled}} data-disabled=\"true\"{{end}}\n     class=\"flex h-9 w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] dark:bg-input/30 focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 has-[input[aria-invalid=true]]:border-destructive has-[input[aria-invalid=true]]:ring-destructive/20 dark:has-[input[aria-invalid=true]]:ring-destructive/40 has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50\"\n     {{- range $k, $v := .Attrs}} {{$k}}=\"{{$v}}\"{{end -}}\n>\n  <button type=\"button\" data-step=\"down\" tabindex=\"-1\"{{if .Disabled}} disabled{{end}} aria-label=\"Decrease\" title=\"Decrease\"\n          class=\"{{$stepperBtn}} rounded-l-md border-r border-input\">\n    <span aria-hidden=\"true\">{{htmlSafe \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" class=\\\"size-4\\\"><path d=\\\"M5 12h14\\\"/></svg>\"}}</span>\n  </button>\n  <input type=\"number\"\n       {{- if .ID}} id=\"{{.ID}}\"{{end}}\n       {{- if .Name}} name=\"{{.Name}}\"{{end}}\n       {{- if .Value}} value=\"{{.Value}}\"{{end}}\n       {{- if .Placeholder}} placeholder=\"{{.Placeholder}}\"{{end}}\n       {{- if .Min}} min=\"{{.Min}}\"{{end}}\n       {{- if .Max}} max=\"{{.Max}}\"{{end}}\n       {{- if .Step}} step=\"{{.Step}}\"{{end}}\n       {{- if .Required}} required{{end}}\n       {{- if .Disabled}} disabled{{end}}\n       {{- if .Readonly}} readonly{{end}}\n       {{- if .Autofocus}} autofocus{{end}}\n       {{- if .Form}} form=\"{{.Form}}\"{{end}}\n       {{- if .List}} list=\"{{.List}}\"{{end}}\n       {{- /* autocomplete: supported common attr on <input type=number> — MDN input/number */ -}}\n       {{- if .Autocomplete}} autocomplete=\"{{.Autocomplete}}\"{{end}}\n       {{- if .InputMode}} inputmode=\"{{.InputMode}}\"{{end}}\n       {{- if .AriaLabel}} aria-label=\"{{.AriaLabel}}\"{{end}}\n       {{- if .AriaLabelledby}} aria-labelledby=\"{{.AriaLabelledby}}\"{{end}}\n       {{- if .AriaDescribedby}} aria-describedby=\"{{.AriaDescribedby}}\"{{end}}\n       {{- /* aria-valuetext: human-readable spinbutton value — APG spinbutton pattern */ -}}\n       {{- if .AriaValuetext}} aria-valuetext=\"{{.AriaValuetext}}\"{{end}}\n       {{- if .AriaInvalid}} aria-invalid=\"{{.AriaInvalid}}\"{{end}}\n       {{- if .AriaRequired}} aria-required=\"{{.AriaRequired}}\"{{end}}\n       data-slot=\"number-input-field\"\n       class=\"{{$inputBase}} rounded-none border-0 text-center shadow-none focus-visible:ring-0\">\n  <button type=\"button\" data-step=\"up\" tabindex=\"-1\"{{if .Disabled}} disabled{{end}} aria-label=\"Increase\" title=\"Increase\"\n          class=\"{{$stepperBtn}} rounded-r-md border-l border-input\">\n    <span aria-hidden=\"true\">{{htmlSafe \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" class=\\\"size-4\\\"><path d=\\\"M5 12h14\\\"/><path d=\\\"M12 5v14\\\"/></svg>\"}}</span>\n  </button>\n</div>\n{{- else -}}\n<input type=\"number\"\n     {{- if .ID}} id=\"{{.ID}}\"{{end}}\n     {{- if .Name}} name=\"{{.Name}}\"{{end}}\n     {{- if .Value}} value=\"{{.Value}}\"{{end}}\n     {{- if .Placeholder}} placeholder=\"{{.Placeholder}}\"{{end}}\n     {{- if .Min}} min=\"{{.Min}}\"{{end}}\n     {{- if .Max}} max=\"{{.Max}}\"{{end}}\n     {{- if .Step}} step=\"{{.Step}}\"{{end}}\n     {{- if .Required}} required{{end}}\n     {{- if .Disabled}} disabled{{end}}\n     {{- if .Readonly}} readonly{{end}}\n     {{- if .Autofocus}} autofocus{{end}}\n     {{- if .Form}} form=\"{{.Form}}\"{{end}}\n     {{- if .List}} list=\"{{.List}}\"{{end}}\n     {{- /* autocomplete: supported common attr on <input type=number> — MDN input/number */ -}}\n     {{- if .Autocomplete}} autocomplete=\"{{.Autocomplete}}\"{{end}}\n     {{- if .InputMode}} inputmode=\"{{.InputMode}}\"{{end}}\n     {{- if .AriaLabel}} aria-label=\"{{.AriaLabel}}\"{{end}}\n     {{- if .AriaLabelledby}} aria-labelledby=\"{{.AriaLabelledby}}\"{{end}}\n     {{- if .AriaDescribedby}} aria-describedby=\"{{.AriaDescribedby}}\"{{end}}\n     {{- /* aria-valuetext: human-readable spinbutton value — APG spinbutton pattern */ -}}\n     {{- if .AriaValuetext}} aria-valuetext=\"{{.AriaValuetext}}\"{{end}}\n     {{- if .AriaInvalid}} aria-invalid=\"{{.AriaInvalid}}\"{{end}}\n     {{- if .AriaRequired}} aria-required=\"{{.AriaRequired}}\"{{end}}\n     data-slot=\"number-input\"\n     class=\"{{$inputBase}}\"\n     {{- range $k, $v := .Attrs}} {{$k}}=\"{{$v}}\"{{end -}}\n>\n{{- end -}}\n{{end}}\n"
    },
    {
      "path": "registry/phoenix/number_input.ex",
      "type": "registry:file",
      "target": "lib/my_app_web/components/number_input.ex",
      "content": "defmodule ShadcnHtmx.Components.NumberInput do\n  @moduledoc \"\"\"\n  Number Input — shadcn-htmx, htmx v4 + Tailwind v4 for Phoenix.\n\n  Mirrors registry/ui/number-input.tsx. The control is a native\n  `<input type=\"number\">`, so role=spinbutton, aria-valuenow/min/max, and the\n  ArrowUp/ArrowDown stepping contract all come from the platform. The optional\n  −/+ buttons call `stepDown()` / `stepUp()` through the shared handler in\n  public/site.js (keyed on `data-slot=\"number-input\"`).\n\n  ## Examples\n\n      <.number_input name=\"qty\" value={1} min={0} max={10} aria-label=\"Quantity\" />\n      <.number_input name=\"price\" min={0} step=\"0.01\" inputmode=\"decimal\" steppers={false} />\n\n  See repos/mdn/files/en-us/web/html/reference/elements/input/number/index.md.\n  \"\"\"\n\n  use Phoenix.Component\n\n  @input_base \"flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs \" <>\n                \"transition-[color,box-shadow] outline-none \" <>\n                \"selection:bg-primary selection:text-primary-foreground \" <>\n                \"placeholder:text-muted-foreground \" <>\n                \"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 \" <>\n                \"md:text-sm dark:bg-input/30 \" <>\n                \"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 \" <>\n                \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 \" <>\n                \"[&.htmx-request]:opacity-70 \" <>\n                \"[&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]\"\n\n  @stepper_btn \"inline-flex size-9 shrink-0 items-center justify-center text-muted-foreground transition-colors outline-none select-none \" <>\n                 \"hover:text-foreground hover:bg-accent \" <>\n                 \"focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:relative focus-visible:z-10 \" <>\n                 \"disabled:pointer-events-none disabled:opacity-50\"\n\n  attr :steppers, :boolean, default: true\n  attr :disabled, :boolean, default: false\n  attr :class, :string, default: nil\n\n  attr :rest, :global,\n    include:\n      # autocomplete: supported common attr on <input type=number> (MDN input/number).\n      # aria-valuetext: human-readable spinbutton value (APG spinbutton pattern).\n      ~w(hx-get hx-post hx-put hx-patch hx-delete hx-target hx-swap hx-trigger hx-indicator hx-vals hx-include\n         id name value placeholder min max step required readonly autofocus form list autocomplete inputmode\n         aria-label aria-labelledby aria-describedby aria-valuetext aria-invalid aria-required)\n\n  def number_input(assigns) do\n    assigns =\n      assigns\n      |> assign(:input_base, @input_base)\n      |> assign(:stepper_btn, @stepper_btn)\n\n    ~H\"\"\"\n    <div\n      :if={@steppers}\n      data-slot=\"number-input\"\n      data-disabled={@disabled && \"true\"}\n      class={[\n        \"flex h-9 w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] dark:bg-input/30\",\n        \"focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50\",\n        \"has-[input[aria-invalid=true]]:border-destructive has-[input[aria-invalid=true]]:ring-destructive/20 dark:has-[input[aria-invalid=true]]:ring-destructive/40\",\n        \"has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50\",\n        @class\n      ]}\n    >\n      <button\n        type=\"button\"\n        data-step=\"down\"\n        tabindex=\"-1\"\n        disabled={@disabled}\n        aria-label=\"Decrease\"\n        title=\"Decrease\"\n        class={[@stepper_btn, \"rounded-l-md border-r border-input\"]}\n      >\n        <span aria-hidden=\"true\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\">\n            <path d=\"M5 12h14\" />\n          </svg>\n        </span>\n      </button>\n      <input\n        type=\"number\"\n        disabled={@disabled}\n        data-slot=\"number-input-field\"\n        class={[@input_base, \"rounded-none border-0 text-center shadow-none focus-visible:ring-0\"]}\n        {@rest}\n      />\n      <button\n        type=\"button\"\n        data-step=\"up\"\n        tabindex=\"-1\"\n        disabled={@disabled}\n        aria-label=\"Increase\"\n        title=\"Increase\"\n        class={[@stepper_btn, \"rounded-r-md border-l border-input\"]}\n      >\n        <span aria-hidden=\"true\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\">\n            <path d=\"M5 12h14\" />\n            <path d=\"M12 5v14\" />\n          </svg>\n        </span>\n      </button>\n    </div>\n    <input\n      :if={!@steppers}\n      type=\"number\"\n      disabled={@disabled}\n      data-slot=\"number-input\"\n      class={[@input_base, @class]}\n      {@rest}\n    />\n    \"\"\"\n  end\nend\n"
    },
    {
      "path": "registry/html/number-input.html",
      "type": "registry:file",
      "target": "snippets/number-input.html",
      "content": "<!--\n  shadcn-htmx — raw Number Input snippets.\n\n  Mirrors registry/ui/number-input.tsx. Drop onto any page that loads Tailwind\n  CSS v4 and the shadcn theme variables (background, foreground, input, ring,\n  destructive, accent, muted-foreground). See app/styles/input.css for defaults.\n\n  The control is a native <input type=\"number\"> — role=spinbutton,\n  aria-valuenow/min/max, and the ArrowUp/ArrowDown stepping contract all come\n  from the platform (see MDN). The −/+ buttons call stepDown()/stepUp(); the\n  inline boot script below wires them (data-slot=\"number-input\"). In the docs\n  app the same handler lives in public/site.js, so you can delete the script\n  there. With the bare-field variant there is no JS at all.\n\n  INPUT BASE (shared with Input + hides native spinners):\n    flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent\n    px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none\n    selection:bg-primary selection:text-primary-foreground\n    placeholder:text-muted-foreground\n    disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50\n    md:text-sm dark:bg-input/30\n    focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50\n    aria-invalid:border-destructive aria-invalid:ring-destructive/20\n    dark:aria-invalid:ring-destructive/40 [&.htmx-request]:opacity-70\n    [&::-webkit-inner-spin-button]:appearance-none\n    [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]\n-->\n\n<!-- ─── With steppers (default) ─────────────────────────────────────── -->\n<div data-slot=\"number-input\"\n  class=\"flex h-9 w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] dark:bg-input/30 focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 has-[input[aria-invalid=true]]:border-destructive has-[input[aria-invalid=true]]:ring-destructive/20 dark:has-[input[aria-invalid=true]]:ring-destructive/40 has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50\">\n  <button type=\"button\" data-step=\"down\" tabindex=\"-1\" aria-label=\"Decrease\" title=\"Decrease\"\n    class=\"inline-flex size-9 shrink-0 items-center justify-center text-muted-foreground transition-colors outline-none select-none hover:text-foreground hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:relative focus-visible:z-10 disabled:pointer-events-none disabled:opacity-50 rounded-l-md border-r border-input\">\n    <span aria-hidden=\"true\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\"><path d=\"M5 12h14\"/></svg></span>\n  </button>\n  <!-- aria-valuetext: human-readable spinbutton value for AT (APG spinbutton pattern). -->\n  <input type=\"number\" name=\"qty\" value=\"1\" min=\"0\" max=\"10\" aria-valuetext=\"1 item\" data-slot=\"number-input-field\"\n    class=\"flex h-9 w-full min-w-0 rounded-none border-0 bg-transparent px-3 py-1 text-base text-center shadow-none outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:ring-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]\">\n  <button type=\"button\" data-step=\"up\" tabindex=\"-1\" aria-label=\"Increase\" title=\"Increase\"\n    class=\"inline-flex size-9 shrink-0 items-center justify-center text-muted-foreground transition-colors outline-none select-none hover:text-foreground hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:relative focus-visible:z-10 disabled:pointer-events-none disabled:opacity-50 rounded-r-md border-l border-input\">\n    <span aria-hidden=\"true\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\"><path d=\"M5 12h14\"/><path d=\"M12 5v14\"/></svg></span>\n  </button>\n</div>\n\n<!-- ─── Bare field, no buttons — zero JS ────────────────────────────── -->\n<!-- Still a full spinbutton: ArrowUp/ArrowDown step, native value clamping. -->\n<!-- autocomplete: supported common attr on <input type=number> for browser autofill (MDN input/number). -->\n<input type=\"number\" name=\"amount\" min=\"0\" step=\"0.01\" inputmode=\"decimal\" placeholder=\"0.00\" autocomplete=\"transaction-amount\" data-slot=\"number-input\"\n  class=\"flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]\">\n\n<!-- ─── Disabled ────────────────────────────────────────────────────── -->\n<div data-slot=\"number-input\" data-disabled=\"true\"\n  class=\"flex h-9 w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent shadow-xs dark:bg-input/30 has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50\">\n  <button type=\"button\" data-step=\"down\" tabindex=\"-1\" disabled aria-label=\"Decrease\" title=\"Decrease\"\n    class=\"inline-flex size-9 shrink-0 items-center justify-center text-muted-foreground select-none disabled:pointer-events-none disabled:opacity-50 rounded-l-md border-r border-input\">\n    <span aria-hidden=\"true\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\"><path d=\"M5 12h14\"/></svg></span>\n  </button>\n  <input type=\"number\" value=\"3\" disabled data-slot=\"number-input-field\"\n    class=\"flex h-9 w-full min-w-0 rounded-none border-0 bg-transparent px-3 py-1 text-base text-center shadow-none outline-none md:text-sm [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]\">\n  <button type=\"button\" data-step=\"up\" tabindex=\"-1\" disabled aria-label=\"Increase\" title=\"Increase\"\n    class=\"inline-flex size-9 shrink-0 items-center justify-center text-muted-foreground select-none disabled:pointer-events-none disabled:opacity-50 rounded-r-md border-l border-input\">\n    <span aria-hidden=\"true\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\"><path d=\"M5 12h14\"/><path d=\"M12 5v14\"/></svg></span>\n  </button>\n</div>\n\n<!--\n  Boot script — wires the −/+ buttons to the native stepDown()/stepUp().\n  Self-contained; safe to include once per page. (The docs site ships the\n  same logic in public/site.js, so omit this there.)\n-->\n<script>\n  (function () {\n    document.addEventListener('click', function (e) {\n      var btn = e.target.closest && e.target.closest('[data-slot=\"number-input\"] [data-step]')\n      if (!btn || btn.disabled) return\n      var root = btn.closest('[data-slot=\"number-input\"]')\n      var input = root && root.querySelector('input[type=\"number\"]')\n      if (!input || input.disabled || input.readOnly) return\n      try {\n        if (btn.getAttribute('data-step') === 'up') input.stepUp()\n        else input.stepDown()\n      } catch (err) { return }\n      input.dispatchEvent(new Event('input', { bubbles: true }))\n      input.dispatchEvent(new Event('change', { bubbles: true }))\n    })\n  })()\n</script>\n"
    }
  ]
}
