{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pricing-02",
  "type": "registry:block",
  "title": "Usage-based Pricing Tiers",
  "description": "A usage-based pricing table with horizontal tiers, highlighted plans, and clear host-owned purchase actions, adapted from Tailark.",
  "author": "Irung",
  "registryDependencies": [],
  "dependencies": [
    "@base-ui/react@1.9.0",
    "class-variance-authority@0.7.1",
    "cn@0.2.6",
    "lucide-react@0.577.0"
  ],
  "files": [
    {
      "path": "licenses/pricing-02/LICENSE.txt",
      "type": "registry:file",
      "target": "@components/uiblocks/pricing-02/LICENSE.txt",
      "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\nUIBlocks modifications (block v0.1.0):\n- Replaced Next.js Link and upstream Card variants with portable anchors and local primitives.\n- Added typed tiers, host selection callbacks, disabled unavailable actions and safe controlled defaults.\n- Reserved fictional prices and plan claims for explicit demo mode.\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\nThird-party design: Tailark\nBlock: pricing-02 (v0.1.0)\nSource: https://github.com/tailark/blocks\nAuthor: Irung\nRevision: 8139698115c1341bfd2e3e286c04bb4d8146f472\nOriginal files:\n- registry/bases/base/veil/blocks/pricing/two.tsx\nUIBlocks modifications:\n- Replaced Next.js Link and upstream Card variants with portable anchors and local primitives.\n- Added typed tiers, host selection callbacks, disabled unavailable actions and safe controlled defaults.\n- Reserved fictional prices and plan claims for explicit demo mode.\n\nMIT License\n\nCopyright (c) 2025 Irung\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
    },
    {
      "path": "content/components/marketing/pricing-02.tsx",
      "type": "registry:component",
      "target": "@components/uiblocks/pricing-02/pricing-02.tsx",
      "content": "\"use client\";\n\n/**\n * Adapted from Tailark by Irung. Copyright (c) 2025 Irung. MIT licensed.\n * Source: https://github.com/tailark/blocks/blob/8139698115c1341bfd2e3e286c04bb4d8146f472/registry/bases/base/veil/blocks/pricing/two.tsx\n * Adaptations: Next.js Link replaced with host anchors; local Card/Button APIs;\n * typed plans and callbacks; explicit demo mode; pending/error handling and wrapping.\n */\nimport { ArrowRight } from \"lucide-react\";\nimport { useId, useState } from \"react\";\nimport { Button } from \"./shared/components/ui/button\";\nimport { Card } from \"./shared/components/ui/card\";\nimport { cn } from \"./shared/lib/utils\";\n\nexport type Pricing02Tier = {\n  id: string;\n  name: string;\n  description?: string;\n  price: string;\n  period?: string;\n  limit?: string;\n  highlighted?: boolean;\n  actionLabel?: string;\n  href?: string;\n  disabled?: boolean;\n};\n\nexport type Pricing02Props = {\n  mode?: \"controlled\" | \"demo\";\n  title?: string;\n  description?: string;\n  tiers?: readonly Pricing02Tier[];\n  usageNote?: { title: string; description?: string };\n  onSelectPlan?: (tier: Pricing02Tier) => void | Promise<void>;\n  className?: string;\n};\n\nconst demoTiers: readonly Pricing02Tier[] = [\n  {\n    id: \"demo-hobby\",\n    name: \"Hobby\",\n    description: \"For personal projects\",\n    price: \"$0\",\n    period: \"/month\",\n    limit: \"1,000 example requests/month\",\n  },\n  {\n    id: \"demo-pro\",\n    name: \"Pro\",\n    description: \"For professional use\",\n    price: \"$20\",\n    period: \"/month\",\n    limit: \"50,000 example requests/month\",\n    highlighted: true,\n  },\n  {\n    id: \"demo-scale\",\n    name: \"Scale\",\n    description: \"For high-volume apps\",\n    price: \"$100\",\n    period: \"/month\",\n    limit: \"500,000 example requests/month\",\n  },\n  {\n    id: \"demo-enterprise\",\n    name: \"Enterprise\",\n    description: \"For larger teams\",\n    price: \"Custom\",\n    limit: \"Host-defined allowance\",\n  },\n];\n\nexport default function Pricing02({\n  mode = \"controlled\",\n  title = \"Plans and usage\",\n  description,\n  tiers,\n  usageNote,\n  onSelectPlan,\n  className,\n}: Pricing02Props) {\n  const headingId = useId();\n  const [pending, setPending] = useState<string | null>(null);\n  const [error, setError] = useState(\"\");\n  const usingDemoTiers = mode === \"demo\" && tiers === undefined;\n  const plans = tiers ?? (mode === \"demo\" ? demoTiers : []);\n  const note =\n    usageNote ??\n    (mode === \"demo\"\n      ? {\n          title: \"Need a different allowance?\",\n          description:\n            \"Example pricing only. Set your verified limits, overage policy, and host checkout action before use.\",\n        }\n      : undefined);\n  async function selectPlan(tier: Pricing02Tier) {\n    if (!onSelectPlan || usingDemoTiers || pending) return;\n    setPending(tier.id);\n    setError(\"\");\n    try {\n      await onSelectPlan(tier);\n    } catch (failure) {\n      setError(\n        failure instanceof Error\n          ? failure.message\n          : \"The plan action could not be completed. Try again.\"\n      );\n    } finally {\n      setPending(null);\n    }\n  }\n  return (\n    <section\n      aria-labelledby={headingId}\n      className={cn(\n        \"@container bg-background py-16 text-foreground sm:py-24\",\n        className\n      )}\n      data-pricing-mode={mode}\n    >\n      <div className=\"mx-auto max-w-3xl px-6\">\n        <div className=\"text-center\">\n          <h2\n            id={headingId}\n            className=\"break-words font-serif text-3xl font-medium sm:text-4xl\"\n          >\n            {title}\n          </h2>\n          {description && (\n            <p className=\"mx-auto mt-4 max-w-md text-balance leading-7 text-muted-foreground [overflow-wrap:anywhere]\">\n              {description}\n            </p>\n          )}\n          {mode === \"demo\" && (\n            <p className=\"mt-3 text-xs leading-6 text-muted-foreground\">\n              Demo plans · Sample pricing is illustrative; actions are supplied\n              by the host.\n            </p>\n          )}\n        </div>\n        {error && (\n          <p\n            className=\"mt-6 rounded-xl border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive [overflow-wrap:anywhere]\"\n            role=\"alert\"\n          >\n            {error}\n          </p>\n        )}\n        <div className=\"mt-12 space-y-3\" aria-busy={pending !== null}>\n          {plans.length ? (\n            plans.map((tier) => {\n              const disabled = Boolean(tier.disabled || pending !== null);\n              const actionClass =\n                \"h-auto min-h-9 max-w-full gap-1 whitespace-normal px-3 py-2 text-center motion-reduce:transition-none\";\n              const actionLabel =\n                pending === tier.id\n                  ? \"Opening…\"\n                  : (tier.actionLabel ?? \"Choose plan\");\n              const actionContent = (\n                <>\n                  {actionLabel}\n                  <ArrowRight aria-hidden=\"true\" className=\"size-4 shrink-0\" />\n                </>\n              );\n              return (\n                <Card\n                  key={tier.id}\n                  className={cn(\n                    \"flex min-w-0 flex-col gap-4 border border-border p-4 ring-0 @2xl:flex-row @2xl:items-center @2xl:justify-between\",\n                    tier.highlighted && \"border-primary ring-1 ring-primary\"\n                  )}\n                >\n                  <div className=\"flex min-w-0 flex-1 flex-col gap-2 @2xl:flex-row @2xl:items-center @2xl:gap-6\">\n                    <div className=\"min-w-0 shrink-0 @2xl:w-44\">\n                      <h3 className=\"break-words font-medium [overflow-wrap:anywhere]\">\n                        {tier.name}\n                      </h3>\n                      {tier.description && (\n                        <p className=\"mt-1 text-sm leading-6 text-muted-foreground [overflow-wrap:anywhere]\">\n                          {tier.description}\n                        </p>\n                      )}\n                    </div>\n                    {tier.limit && (\n                      <div className=\"min-w-0 @2xl:border-l @2xl:pl-6\">\n                        <p className=\"text-sm leading-6 text-muted-foreground [overflow-wrap:anywhere]\">\n                          {tier.limit}\n                        </p>\n                      </div>\n                    )}\n                  </div>\n                  <div className=\"flex min-w-0 flex-col gap-4 @2xl:max-w-[48%] @2xl:shrink-0 @2xl:flex-row @2xl:items-center\">\n                    <div className=\"min-w-0 @2xl:text-right\">\n                      <span className=\"break-all font-serif text-2xl font-medium\">\n                        {tier.price}\n                      </span>\n                      {tier.period && (\n                        <span className=\"text-sm text-muted-foreground [overflow-wrap:anywhere]\">\n                          {tier.period}\n                        </span>\n                      )}\n                    </div>\n                    {onSelectPlan && !usingDemoTiers ? (\n                      <Button\n                        className={actionClass}\n                        size=\"sm\"\n                        variant={tier.highlighted ? \"default\" : \"outline\"}\n                        disabled={disabled}\n                        type=\"button\"\n                        onClick={() => {\n                          void selectPlan(tier);\n                        }}\n                      >\n                        {actionContent}\n                      </Button>\n                    ) : tier.href && !disabled ? (\n                      <Button\n                        nativeButton={false}\n                        render={<a href={tier.href} />}\n                        className={actionClass}\n                        size=\"sm\"\n                        variant={tier.highlighted ? \"default\" : \"outline\"}\n                      >\n                        {actionContent}\n                      </Button>\n                    ) : (\n                      <Button\n                        className={actionClass}\n                        size=\"sm\"\n                        variant={tier.highlighted ? \"default\" : \"outline\"}\n                        disabled\n                        type=\"button\"\n                        title=\"Supply a host URL or onSelectPlan callback\"\n                      >\n                        {actionContent}\n                      </Button>\n                    )}\n                  </div>\n                </Card>\n              );\n            })\n          ) : (\n            <p className=\"rounded-xl border border-dashed border-border p-8 text-sm leading-7 text-muted-foreground\">\n              No plans supplied. Pass verified pricing, limits, and your host\n              action through the tiers prop.\n            </p>\n          )}\n        </div>\n        {note && (\n          <aside className=\"mt-8 rounded-xl bg-muted p-6 text-center\">\n            <h3 className=\"break-words font-medium\">{note.title}</h3>\n            {note.description && (\n              <p className=\"mt-1 text-sm leading-6 text-muted-foreground [overflow-wrap:anywhere]\">\n                {note.description}\n              </p>\n            )}\n          </aside>\n        )}\n      </div>\n    </section>\n  );\n}\n"
    },
    {
      "path": "components/ui/button.tsx",
      "type": "registry:component",
      "target": "@components/uiblocks/pricing-02/shared/components/ui/button.tsx",
      "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
    },
    {
      "path": "components/ui/card.tsx",
      "type": "registry:component",
      "target": "@components/uiblocks/pricing-02/shared/components/ui/card.tsx",
      "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
    },
    {
      "path": "lib/utils.ts",
      "type": "registry:component",
      "target": "@components/uiblocks/pricing-02/shared/lib/utils.ts",
      "content": "export { cn } from \"cn\";\n"
    }
  ],
  "categories": [
    "marketing",
    "marketing",
    "clean-saas"
  ],
  "docs": "Import the default component from @components/uiblocks/pricing-02/pricing-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. The live preview opts into mode='demo'; the installed component defaults to controlled data and actions. Supply the host's records and callbacks before enabling service actions. Original design: Tailark by Irung, commit 8139698115c1341bfd2e3e286c04bb4d8146f472, source https://github.com/tailark/blocks. Original paths and local adaptations are recorded in meta.source. The complete upstream MIT notice is bundled in LICENSE.txt; retain it when adapting the component. See https://uiblocks.org/marketing/pricing-02 for content guidelines and source attribution.",
  "meta": {
    "kind": "block",
    "previewProps": {
      "mode": "demo"
    },
    "defaultMode": "controlled",
    "demoMode": "explicit",
    "includesBackend": false,
    "version": "0.1.0",
    "source": {
      "name": "Tailark",
      "url": "https://github.com/tailark/blocks",
      "license": "MIT",
      "author": "Irung",
      "revision": "8139698115c1341bfd2e3e286c04bb4d8146f472",
      "licenseFile": "licenses/third-party/tailark-MIT.txt",
      "files": [
        "registry/bases/base/veil/blocks/pricing/two.tsx"
      ],
      "modifications": [
        "Replaced Next.js Link and upstream Card variants with portable anchors and local primitives.",
        "Added typed tiers, host selection callbacks, disabled unavailable actions and safe controlled defaults.",
        "Reserved fictional prices and plan claims for explicit demo mode."
      ]
    },
    "license": "MIT",
    "style": "clean-saas",
    "pageType": "marketing",
    "featured": true,
    "tags": [
      "pricing",
      "plans",
      "tiers",
      "usage",
      "subscription",
      "billing",
      "定价",
      "套餐",
      "用量",
      "订阅",
      "价格表"
    ],
    "contentGuidelines": [
      "Supply real prices, currency, billing periods and usage limits through tiers.",
      "Connect each tier to a host href or onSelectPlan callback; the library never processes payments.",
      "Use explicit demo mode only for illustrative sample plans and verify long labels on mobile."
    ],
    "recommendedWith": [
      "testimonials-01",
      "faq-01",
      "usage-panel-01"
    ],
    "entry": "@components/uiblocks/pricing-02/pricing-02.tsx",
    "exportName": "default",
    "frameworkIndependent": true,
    "frameworkDependencies": [],
    "compatibleHosts": [
      "react",
      "vite",
      "tanstack-start",
      "nextjs"
    ],
    "requirements": {
      "react": ">=19",
      "tailwind": ">=4"
    },
    "previewUrl": "https://uiblocks.org/preview/pricing-02"
  }
}
