{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "copy-button",
  "type": "registry:ui",
  "title": "Copy Button",
  "description": "A native <button> that writes an associated string (snippet, API key, URL) to the clipboard with the Async Clipboard API, then flips to a transient aria-live \"Copied\" state. Progressive-enhancement fallback for non-secure contexts. Ships in five flavours: Hono JSX (TypeScript), Jinja2 macro, Go html/template, Phoenix function component, and a raw HTML snippet. Follows the WAI-ARIA APG button pattern.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/copy-button.tsx",
      "type": "registry:ui",
      "target": "components/ui/copy-button.tsx",
      "content": "/** @jsxImportSource hono/jsx */\nimport { cn, type ClassValue } from \"@/registry/lib/cn\"\n\n// Copy Button — shadcn-htmx, htmx v4 + Tailwind v4.\n//\n// Click-to-copy button: writes an associated string (a snippet, an API key, a\n// URL) to the system clipboard, then flips to a transient \"Copied\" state and\n// announces it through a visually-hidden aria-live region. The docs site's own\n// code-block is a consumer.\n//\n// Built on the Async Clipboard API. navigator.clipboard.writeText(text)\n// returns a Promise that resolves once the system clipboard has been updated;\n// it works only in a secure context (HTTPS / localhost) and from a window that\n// has focus:\n//   repos/mdn/files/en-us/web/api/clipboard/writetext/index.md\n// The shared behaviour in site.js follows web.dev's progressive-enhancement\n// recipe — use the async API when present, otherwise fall back to a throwaway\n// <textarea> + document.execCommand('copy'):\n//   repos/web.dev/src/site/content/en/patterns/clipboard/copy-text/index.md\n//\n// Accessibility:\n//   - A native <button> gives us role=button + Space/Enter activation for free\n//     (APG button pattern: repos/aria-practices/content/patterns/button/).\n//   - The transition is announced through an EMPTY element carrying\n//     aria-live=\"polite\": site.js writes \"Copied\" into it on success. MDN: the\n//     aria-live attribute is set on an empty element that is then populated, so\n//     AT announces the change without moving focus —\n//     repos/mdn/files/en-us/web/accessibility/aria/reference/attributes/aria-live/index.md\n//   - This is NOT a toggle: aria-pressed is wrong here (the label/state is\n//     transient feedback, not a sticky on/off), so we use aria-live instead.\n//\n// All hx-*, data-* and aria-* attributes are forwarded onto the <button> via\n// {...rest}, so the button can also trigger an htmx request if you want one.\n\nexport type CopyButtonVariant = \"outline\" | \"ghost\" | \"secondary\"\nexport type CopyButtonSize = \"default\" | \"sm\" | \"icon\"\n\nconst base =\n  \"inline-flex shrink-0 items-center justify-center gap-1.5 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none \" +\n  \"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 \" +\n  \"disabled:pointer-events-none disabled:opacity-50 \" +\n  \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 \" +\n  // Transient success state. site.js sets data-copied=\"true\" for a beat, then\n  // clears it; we swap the copy glyph for the check and tint it success-green.\n  \"[&[data-copied=true]]:text-emerald-600 dark:[&[data-copied=true]]:text-emerald-400 \" +\n  \"[&_[data-copy-check]]:hidden [&[data-copied=true]_[data-copy-icon]]:hidden [&[data-copied=true]_[data-copy-check]]:inline-flex\"\n\nconst variants: Record<CopyButtonVariant, string> = {\n  outline:\n    \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n  ghost: \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n  secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n}\n\nconst sizes: Record<CopyButtonSize, string> = {\n  default: \"h-8 px-2.5\",\n  sm: \"h-7 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3\",\n  icon: \"size-8 [&]:px-0\",\n}\n\ntype CopyButtonProps = {\n  // The string written to the clipboard. Either pass `value` directly, or\n  // point `copyTarget` at the id of an element whose text/value to copy\n  // (so the docs code-block can be a consumer without duplicating its text).\n  value?: string\n  copyTarget?: string\n  variant?: CopyButtonVariant\n  size?: CopyButtonSize\n  // Visible label next to the icon (default \"Copy\" / \"Copied\"). For size\n  // \"icon\" the label is dropped and the accessible name comes from ariaLabel.\n  label?: string\n  copiedLabel?: string\n  // Accessible name. Required for size=\"icon\"; otherwise the visible label\n  // supplies the name.\n  ariaLabel?: string\n  // Politeness of the success announcement. polite waits for a graceful\n  // pause; assertive interrupts. Default polite (MDN aria-live).\n  live?: \"polite\" | \"assertive\"\n  disabled?: boolean\n  class?: ClassValue\n  id?: string\n  // Forward arbitrary attributes (hx-*, data-*, aria-*, name/value/form, …).\n  [key: string]: any\n}\n\nconst CopyIcon = () => (\n  <svg\n    data-copy-icon\n    xmlns=\"http://www.w3.org/2000/svg\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-width=\"2\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    aria-hidden=\"true\"\n  >\n    <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n    <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n  </svg>\n)\n\nconst CheckIcon = () => (\n  <svg\n    data-copy-check\n    xmlns=\"http://www.w3.org/2000/svg\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-width=\"2\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    aria-hidden=\"true\"\n  >\n    <path d=\"M20 6 9 17l-5-5\" />\n  </svg>\n)\n\nexport function CopyButton(props: CopyButtonProps) {\n  const {\n    value,\n    copyTarget,\n    variant = \"outline\",\n    size = \"default\",\n    label = \"Copy\",\n    copiedLabel = \"Copied\",\n    ariaLabel,\n    live = \"polite\",\n    disabled,\n    class: className,\n    id,\n    ...rest\n  } = props\n\n  const iconOnly = size === \"icon\"\n  const classes = cn(base, variants[variant], sizes[size], className)\n\n  return (\n    <button\n      type=\"button\"\n      id={id}\n      data-slot=\"copy-button\"\n      data-variant={variant}\n      data-size={size}\n      data-copy-text={value}\n      data-copy-target={copyTarget}\n      data-copied-label={copiedLabel}\n      disabled={disabled}\n      aria-label={ariaLabel ?? (iconOnly ? label : undefined)}\n      class={classes}\n      {...rest}\n    >\n      <CopyIcon />\n      <CheckIcon />\n      {!iconOnly && (\n        <span data-copy-label>{label}</span>\n      )}\n      {/* Empty aria-live region — site.js writes \"Copied\" here on success so\n          AT announces it without moving focus. MDN: aria-live on an empty\n          element that is then populated. */}\n      <span class=\"sr-only\" aria-live={live} data-copy-status></span>\n    </button>\n  )\n}\n"
    },
    {
      "path": "registry/jinja2/copy-button.html",
      "type": "registry:file",
      "target": "templates/components/copy-button.html",
      "content": "{# Copy Button macro — shadcn-htmx, htmx v4 + Tailwind v4.\n   Mirrors registry/ui/copy-button.tsx so a Python/Flask/FastAPI/Django project\n   renders the same markup our docs site renders.\n\n   Click-to-copy: writes `value` (or the text of the element named by\n   copy_target) to the clipboard via the Async Clipboard API, then flips to a\n   transient \"Copied\" state announced through an empty aria-live region.\n\n   Built on navigator.clipboard.writeText() with a progressive-enhancement\n   fallback — see the shared behaviour shipped in site.js. Sources:\n     repos/mdn/files/en-us/web/api/clipboard/writetext/index.md\n     repos/web.dev/src/site/content/en/patterns/clipboard/copy-text/index.md\n     repos/mdn/files/en-us/web/accessibility/aria/reference/attributes/aria-live/index.md\n\n   Usage:\n       {% from \"components/copy-button.html\" import copy_button %}\n       {{ copy_button(\"npm i shadcn-htmx\") }}\n       {{ copy_button(copy_target=\"api-key\", size=\"icon\", aria_label=\"Copy API key\") }}\n\n   All hx-* attributes pass through via **attrs (underscores become dashes). #}\n\n{% macro copy_button(\n    value=none,\n    copy_target=none,\n    variant=\"outline\",\n    size=\"default\",\n    label=\"Copy\",\n    copied_label=\"Copied\",\n    aria_label=none,\n    live=\"polite\",\n    disabled=false,\n    extra_class=\"\",\n    **attrs\n) %}\n{%- set base -%}\ninline-flex shrink-0 items-center justify-center gap-1.5 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&[data-copied=true]]:text-emerald-600 dark:[&[data-copied=true]]:text-emerald-400 [&_[data-copy-check]]:hidden [&[data-copied=true]_[data-copy-icon]]:hidden [&[data-copied=true]_[data-copy-check]]:inline-flex\n{%- endset -%}\n\n{%- set variants = {\n    \"outline\": \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n    \"ghost\": \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n    \"secondary\": \"bg-secondary text-secondary-foreground hover:bg-secondary/80\"\n} -%}\n\n{%- set sizes = {\n    \"default\": \"h-8 px-2.5\",\n    \"sm\": \"h-7 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3\",\n    \"icon\": \"size-8 [&]:px-0\"\n} -%}\n\n{%- set icon_only = (size == \"icon\") -%}\n\n<button type=\"button\"\n        data-slot=\"copy-button\" data-variant=\"{{ variant }}\" data-size=\"{{ size }}\"\n        {%- if value is not none %} data-copy-text=\"{{ value }}\"{% endif %}\n        {%- if copy_target %} data-copy-target=\"{{ copy_target }}\"{% endif %}\n        data-copied-label=\"{{ copied_label }}\"\n        class=\"{{ base }} {{ variants[variant] }} {{ sizes[size] }} {{ extra_class }}\"\n        {%- if disabled %} disabled{% endif %}\n        {%- if aria_label %} aria-label=\"{{ aria_label }}\"{% elif icon_only %} aria-label=\"{{ label }}\"{% endif %}\n        {%- for k, v in attrs.items() %} {{ k|replace('_', '-') }}=\"{{ v }}\"{% endfor -%}\n>\n  <svg data-copy-icon 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\" aria-hidden=\"true\">\n    <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n    <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n  </svg>\n  <svg data-copy-check 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\" aria-hidden=\"true\">\n    <path d=\"M20 6 9 17l-5-5\" />\n  </svg>\n  {%- if not icon_only %}<span data-copy-label>{{ label }}</span>{% endif %}\n  <span class=\"sr-only\" aria-live=\"{{ live }}\" data-copy-status></span>\n</button>\n{% endmacro %}\n"
    },
    {
      "path": "registry/go-templates/copy-button.tmpl",
      "type": "registry:file",
      "target": "components/copy-button.tmpl",
      "content": "{{/*\n  Copy Button template — shadcn-htmx, htmx v4 + Tailwind v4.\n  Mirrors registry/ui/copy-button.tsx for Go projects using html/template.\n\n  Click-to-copy: writes Value (or the text of the element named by CopyTarget)\n  to the clipboard via the Async Clipboard API, then flips to a transient\n  \"Copied\" state announced through an empty aria-live region. The shared\n  behaviour ships in site.js. Sources:\n    repos/mdn/files/en-us/web/api/clipboard/writetext/index.md\n    repos/web.dev/src/site/content/en/patterns/clipboard/copy-text/index.md\n    repos/mdn/files/en-us/web/accessibility/aria/reference/attributes/aria-live/index.md\n\n  Usage:\n\n      type CopyButtonArgs struct {\n          Value       string            // text written to the clipboard\n          CopyTarget  string            // OR: id of element whose text to copy\n          Variant     string            // outline | ghost | secondary\n          Size        string            // default | sm | icon\n          Label       string            // visible label (default \"Copy\")\n          CopiedLabel string            // success label (default \"Copied\")\n          AriaLabel   string            // required for size=\"icon\"\n          Live        string            // polite | assertive\n          Disabled    bool\n          Attrs       map[string]string // hx-*, data-*, …\n      }\n\n      tpl.ExecuteTemplate(w, \"copy-button\", CopyButtonArgs{\n          Value: \"npm i shadcn-htmx\",\n      })\n\n  Native <button> → role + Space/Enter activation come for free.\n  See repos/aria-practices/content/patterns/button/.\n*/}}\n\n{{define \"copy-button\"}}\n{{- $variants := dict\n    \"outline\" \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\"\n    \"ghost\" \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\"\n    \"secondary\" \"bg-secondary text-secondary-foreground hover:bg-secondary/80\" -}}\n{{- $sizes := dict\n    \"default\" \"h-8 px-2.5\"\n    \"sm\" \"h-7 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3\"\n    \"icon\" \"size-8 [&]:px-0\" -}}\n{{- $variant := or .Variant \"outline\" -}}\n{{- $size := or .Size \"default\" -}}\n{{- $label := or .Label \"Copy\" -}}\n{{- $copiedLabel := or .CopiedLabel \"Copied\" -}}\n{{- $live := or .Live \"polite\" -}}\n{{- $iconOnly := eq $size \"icon\" -}}\n{{- $base := \"inline-flex shrink-0 items-center justify-center gap-1.5 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&[data-copied=true]]:text-emerald-600 dark:[&[data-copied=true]]:text-emerald-400 [&_[data-copy-check]]:hidden [&[data-copied=true]_[data-copy-icon]]:hidden [&[data-copied=true]_[data-copy-check]]:inline-flex\" -}}\n<button type=\"button\"\n        data-slot=\"copy-button\" data-variant=\"{{$variant}}\" data-size=\"{{$size}}\"\n        {{- if .Value}} data-copy-text=\"{{.Value}}\"{{end}}\n        {{- if .CopyTarget}} data-copy-target=\"{{.CopyTarget}}\"{{end}}\n        data-copied-label=\"{{$copiedLabel}}\"\n        class=\"{{$base}} {{index $variants $variant}} {{index $sizes $size}}\"\n        {{- if .Disabled}} disabled{{end}}\n        {{- if .AriaLabel}} aria-label=\"{{.AriaLabel}}\"{{else if $iconOnly}} aria-label=\"{{$label}}\"{{end}}\n        {{- range $k, $v := .Attrs}} {{$k}}=\"{{$v}}\"{{end -}}\n>\n  <svg data-copy-icon 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\" aria-hidden=\"true\">\n    <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n    <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n  </svg>\n  <svg data-copy-check 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\" aria-hidden=\"true\">\n    <path d=\"M20 6 9 17l-5-5\" />\n  </svg>\n  {{- if not $iconOnly}}<span data-copy-label>{{$label}}</span>{{end}}\n  <span class=\"sr-only\" aria-live=\"{{$live}}\" data-copy-status></span>\n</button>\n{{end}}\n\n{{/*\n  Note: this template uses sprig's `dict` helper. If you don't use sprig,\n  hard-code the class lookup or pass the class string from Go:\n\n      args.Class = computeCopyButtonClass(args.Variant, args.Size)\n\n  and reference {{.Class}} directly in the template.\n*/}}\n"
    },
    {
      "path": "registry/phoenix/copy_button.ex",
      "type": "registry:file",
      "target": "lib/my_app_web/components/copy_button.ex",
      "content": "defmodule ShadcnHtmx.Components.CopyButton do\n  @moduledoc \"\"\"\n  Copy Button — shadcn-htmx, htmx v4 + Tailwind v4 for Phoenix.\n\n  Mirrors registry/ui/copy-button.tsx so a Phoenix LiveView project renders the\n  same markup our docs site renders. Works with plain HEEx too — htmx attributes\n  pass through via `:rest`.\n\n  Click-to-copy: writes `value` (or the text of the element named by\n  `copy_target`) to the clipboard via the Async Clipboard API, then flips to a\n  transient \"Copied\" state announced through an empty aria-live region. The\n  shared behaviour ships in site.js. Sources:\n\n    * repos/mdn/files/en-us/web/api/clipboard/writetext/index.md\n    * repos/web.dev/src/site/content/en/patterns/clipboard/copy-text/index.md\n    * repos/mdn/files/en-us/web/accessibility/aria/reference/attributes/aria-live/index.md\n\n  ## Examples\n\n      <.copy_button value=\"npm i shadcn-htmx\" />\n      <.copy_button copy_target=\"api-key\" size=\"icon\" aria-label=\"Copy API key\" />\n\n  Native `<button>` → role and Space/Enter activation come for free.\n  See repos/aria-practices/content/patterns/button/.\n  \"\"\"\n\n  use Phoenix.Component\n\n  @variants %{\n    \"outline\" =>\n      \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n    \"ghost\" => \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n    \"secondary\" => \"bg-secondary text-secondary-foreground hover:bg-secondary/80\"\n  }\n\n  @sizes %{\n    \"default\" => \"h-8 px-2.5\",\n    \"sm\" => \"h-7 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3\",\n    \"icon\" => \"size-8 [&]:px-0\"\n  }\n\n  @base \"inline-flex shrink-0 items-center justify-center gap-1.5 rounded-md text-sm font-medium \" <>\n          \"whitespace-nowrap transition-all outline-none \" <>\n          \"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 \" <>\n          \"disabled:pointer-events-none disabled:opacity-50 \" <>\n          \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 \" <>\n          \"[&[data-copied=true]]:text-emerald-600 dark:[&[data-copied=true]]:text-emerald-400 \" <>\n          \"[&_[data-copy-check]]:hidden [&[data-copied=true]_[data-copy-icon]]:hidden [&[data-copied=true]_[data-copy-check]]:inline-flex\"\n\n  attr :value, :string, default: nil\n  attr :copy_target, :string, default: nil\n\n  attr :variant, :string, default: \"outline\", values: ~w(outline ghost secondary)\n  attr :size, :string, default: \"default\", values: ~w(default sm icon)\n\n  attr :label, :string, default: \"Copy\"\n  attr :copied_label, :string, default: \"Copied\"\n  attr :live, :string, default: \"polite\", values: ~w(polite assertive)\n  attr :disabled, :boolean, default: false\n  attr :class, :string, default: nil\n\n  attr :rest, :global,\n    include:\n      ~w(hx-get hx-post hx-put hx-patch hx-delete hx-target hx-swap hx-trigger hx-vals\n         id name value form aria-label aria-labelledby aria-describedby)\n\n  def copy_button(assigns) do\n    assigns =\n      assigns\n      |> assign(:variant_class, Map.fetch!(@variants, assigns.variant))\n      |> assign(:size_class, Map.fetch!(@sizes, assigns.size))\n      |> assign(:base_class, @base)\n      |> assign(:icon_only, assigns.size == \"icon\")\n\n    ~H\"\"\"\n    <button\n      type=\"button\"\n      data-slot=\"copy-button\"\n      data-variant={@variant}\n      data-size={@size}\n      data-copy-text={@value}\n      data-copy-target={@copy_target}\n      data-copied-label={@copied_label}\n      class={[@base_class, @variant_class, @size_class, @class]}\n      disabled={@disabled}\n      aria-label={@rest[:\"aria-label\"] || if(@icon_only, do: @label, else: nil)}\n      {@rest}\n    >\n      <svg\n        data-copy-icon\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n        aria-hidden=\"true\"\n      >\n        <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n        <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n      </svg>\n      <svg\n        data-copy-check\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n        aria-hidden=\"true\"\n      >\n        <path d=\"M20 6 9 17l-5-5\" />\n      </svg>\n      <span :if={!@icon_only} data-copy-label>{@label}</span>\n      <span class=\"sr-only\" aria-live={@live} data-copy-status></span>\n    </button>\n    \"\"\"\n  end\nend\n"
    },
    {
      "path": "registry/html/copy-button.html",
      "type": "registry:file",
      "target": "snippets/copy-button.html",
      "content": "<!--\n  shadcn-htmx — raw Copy Button snippet.\n\n  Click-to-copy: writes data-copy-text (or the text of the element named by\n  data-copy-target) to the clipboard via the Async Clipboard API, then flips to\n  a transient \"Copied\" state announced through the empty aria-live region.\n\n  Built on:\n    - navigator.clipboard.writeText(text) — returns a Promise, secure-context\n      only (HTTPS / localhost), window must have focus.\n        repos/mdn/files/en-us/web/api/clipboard/writetext/index.md\n    - Progressive-enhancement fallback (throwaway <textarea> + execCommand)\n      for non-secure contexts / older browsers.\n        repos/web.dev/src/site/content/en/patterns/clipboard/copy-text/index.md\n    - aria-live on an empty element, populated on success, so AT announces the\n      change without moving focus.\n        repos/mdn/files/en-us/web/accessibility/aria/reference/attributes/aria-live/index.md\n\n  Requirements:\n    1. Tailwind CSS v4 (or the Play CDN for quick experiments):\n         <script src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4\"></script>\n    2. The shadcn theme CSS variables (--background, --border, --ring, …) —\n       copy the :root / .dark blocks from app/styles/input.css.\n\n  The inline <script> at the bottom is the SAME behaviour shipped in site.js,\n  scoped to [data-slot=\"copy-button\"]. If you already load site.js, delete it.\n-->\n\n<!-- Default (outline) — copies the literal string in data-copy-text -->\n<button type=\"button\" data-slot=\"copy-button\" data-variant=\"outline\" data-size=\"default\"\n  data-copy-text=\"npm i shadcn-htmx\" data-copied-label=\"Copied\"\n  class=\"inline-flex shrink-0 items-center justify-center gap-1.5 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&[data-copied=true]]:text-emerald-600 dark:[&[data-copied=true]]:text-emerald-400 [&_[data-copy-check]]:hidden [&[data-copied=true]_[data-copy-icon]]:hidden [&[data-copied=true]_[data-copy-check]]:inline-flex border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50 h-8 px-2.5\">\n  <svg data-copy-icon 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\" aria-hidden=\"true\">\n    <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" /><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n  </svg>\n  <svg data-copy-check 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\" aria-hidden=\"true\">\n    <path d=\"M20 6 9 17l-5-5\" />\n  </svg>\n  <span data-copy-label>Copy</span>\n  <span class=\"sr-only\" aria-live=\"polite\" data-copy-status></span>\n</button>\n\n<!-- Icon-only (ghost) — accessible name from aria-label, no visible text -->\n<button type=\"button\" data-slot=\"copy-button\" data-variant=\"ghost\" data-size=\"icon\"\n  data-copy-text=\"sk_live_51H8xExampleKeyDoNotUse\" data-copied-label=\"Copied\" aria-label=\"Copy API key\"\n  class=\"inline-flex shrink-0 items-center justify-center gap-1.5 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&[data-copied=true]]:text-emerald-600 dark:[&[data-copied=true]]:text-emerald-400 [&_[data-copy-check]]:hidden [&[data-copied=true]_[data-copy-icon]]:hidden [&[data-copied=true]_[data-copy-check]]:inline-flex hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 size-8 [&]:px-0\">\n  <svg data-copy-icon 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\" aria-hidden=\"true\">\n    <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" /><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n  </svg>\n  <svg data-copy-check 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\" aria-hidden=\"true\">\n    <path d=\"M20 6 9 17l-5-5\" />\n  </svg>\n  <span class=\"sr-only\" aria-live=\"polite\" data-copy-status></span>\n</button>\n\n<!-- Copy from another element: data-copy-target points at an id; we copy that\n     element's value (form fields) or textContent. -->\n<input id=\"share-url\" type=\"text\" readonly value=\"https://shadcn-htmx.dev/r/copy-button.json\"\n  class=\"h-8 rounded-md border bg-background px-2.5 text-sm\" />\n<button type=\"button\" data-slot=\"copy-button\" data-variant=\"secondary\" data-size=\"default\"\n  data-copy-target=\"share-url\" data-copied-label=\"Copied\"\n  class=\"inline-flex shrink-0 items-center justify-center gap-1.5 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&[data-copied=true]]:text-emerald-600 dark:[&[data-copied=true]]:text-emerald-400 [&_[data-copy-check]]:hidden [&[data-copied=true]_[data-copy-icon]]:hidden [&[data-copied=true]_[data-copy-check]]:inline-flex bg-secondary text-secondary-foreground hover:bg-secondary/80 h-8 px-2.5\">\n  <svg data-copy-icon 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\" aria-hidden=\"true\">\n    <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" /><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n  </svg>\n  <svg data-copy-check 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\" aria-hidden=\"true\">\n    <path d=\"M20 6 9 17l-5-5\" />\n  </svg>\n  <span data-copy-label>Copy URL</span>\n  <span class=\"sr-only\" aria-live=\"polite\" data-copy-status></span>\n</button>\n\n<!--\n  Behaviour (delete if you load site.js, which already ships this scoped to\n  [data-slot=\"copy-button\"]). Async Clipboard API with execCommand fallback.\n-->\n<script>\n  (function () {\n    var COPIED_MS = 2000\n    function textFor(btn) {\n      var targetId = btn.getAttribute('data-copy-target')\n      if (targetId) {\n        var el = document.getElementById(targetId)\n        if (el) return 'value' in el && el.value != null ? el.value : (el.textContent || '')\n      }\n      return btn.getAttribute('data-copy-text') || ''\n    }\n    function legacyCopy(text) {\n      var ta = document.createElement('textarea')\n      ta.value = text\n      ta.setAttribute('readonly', '')\n      ta.style.position = 'fixed'\n      ta.style.opacity = '0'\n      document.body.appendChild(ta)\n      ta.focus()\n      ta.select()\n      var ok = false\n      try { ok = document.execCommand('copy') } catch (e) { ok = false }\n      document.body.removeChild(ta)\n      return ok\n    }\n    function flash(btn) {\n      var copied = btn.getAttribute('data-copied-label') || 'Copied'\n      var status = btn.querySelector('[data-copy-status]')\n      var label = btn.querySelector('[data-copy-label]')\n      var prevLabel = label ? label.textContent : null\n      btn.setAttribute('data-copied', 'true')\n      if (label) label.textContent = copied\n      if (status) status.textContent = copied\n      if (btn._copyTimer) clearTimeout(btn._copyTimer)\n      btn._copyTimer = setTimeout(function () {\n        btn.removeAttribute('data-copied')\n        if (label && prevLabel != null) label.textContent = prevLabel\n        if (status) status.textContent = ''\n      }, COPIED_MS)\n    }\n    document.addEventListener('click', function (e) {\n      var btn = e.target.closest && e.target.closest('[data-slot=\"copy-button\"]')\n      if (!btn || btn.disabled) return\n      var text = textFor(btn)\n      if (navigator.clipboard && navigator.clipboard.writeText) {\n        navigator.clipboard.writeText(text).then(\n          function () { flash(btn) },\n          function () { if (legacyCopy(text)) flash(btn) }\n        )\n      } else if (legacyCopy(text)) {\n        flash(btn)\n      }\n    })\n  })()\n</script>\n"
    }
  ]
}
