# Rate Card 2 - Two-plan monthly rate cards

> A pair of pricing cards for a monthly retainer and a premium package, each with a description, included-benefits list, price, and a get-started button.

A shadcn/ui block from the [@shadcnblocks](https://shadcnblocks.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcnblocks/rate-card2).

- Page, with a live preview: https://shadcn.exchange/r/shadcnblocks/rate-card2
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcnblocks/rate-card2-light-12b19b3bb125dde2.png
- Registry item: https://shadcnblocks.com/r/rate-card2.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcnblocks.com/r/rate-card2.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `cn`, `lucide-react`
- Registry items: `button`

## Files

### `block/rate-card2/rate-card2.tsx`

```tsx
"use client";

import { Target } from "lucide-react";
import React from "react";
import { cn } from "cn";

import { BorderButton } from "@/components/shadcnblocks/border-button";
import { useGoogleFont } from "@/hooks/use-google-font";
interface RateCard2Props {
  className?: string;
}

const RateCard2 = ({ className }: RateCard2Props) => {
  const pricingPlans = [
    {
      title: "Monthly retainer",
      description:
        "Lorem ipsum dolor sit amet consectetur adipisicing elit. A quidem libero perferendis eius sunt",
      price: "$3,499",
      period: "/Month",
      features: [
        "Up to 2 active request at a time",
        "Unlimited revisions included",
        "24/7 priority support access",
        "Dedicated account manager",
      ],
    },
    {
      title: "Premium Package",
      description:
        "Lorem ipsum dolor sit amet consectetur adipisicing elit. A quidem libero perferendis eius sunt doloremque natus!",
      price: "$5,999",
      period: "/Month",
      features: [
        "Up to 5 active requests at a time",
        "Advanced analytics dashboard",
        "Custom integrations available",
        "Weekly strategy consultations",
      ],
    },
  ];

  useGoogleFont("Antonio");
  return (
    <section
      className={cn("bg-background py-32", className)}
      style={
        {
          "--font-mono": "Antonio",
        } as React.CSSProperties
      }
    >
      <div className="container flex flex-col items-center justify-center gap-8 lg:gap-15">
        <div className="flex w-full max-w-6xl flex-col items-center justify-between gap-4 lg:flex-row">
          {pricingPlans.map((plan, index) => (
            <div
              key={index}
              className="flex w-full flex-col justify-between gap-10 rounded-3xl bg-muted p-10"
            >
              <div>
                <h2 className="text-3xl font-semibold tracking-tight">
                  {plan.title}
                </h2>
                <p className="mt-4 w-full max-w-xs text-lg tracking-tight text-foreground/30">
                  {plan.description}
                </p>
              </div>
              <ul className="mt-5">
                <li className="mb-4 text-xs font-medium text-foreground/40 uppercase">
                  WHATS INCLUDED:
                </li>
                {plan.features.map((feature, featureIndex) => (
                  <li
                    key={featureIndex}
                    className="mt-1 flex items-center gap-2 font-medium"
                  >
                    <Target className="size-4" />
                    {feature}
                  </li>
                ))}
              </ul>
              <div className="mt-10 flex items-end justify-between">
                <h1 className="text-4xl font-medium tracking-tighter lg:text-7xl">
                  <span className="font-mono">{plan.price}</span>
                  <sup className="pl-2 align-top text-sm font-normal tracking-normal tracking-tight lg:text-base">
                    {plan.period}
                  </sup>
                </h1>
                <BorderButton
                  variant="outline"
                  className="relative rounded-none !px-4 uppercase shadow-none"
                >
                  Get started
                </BorderButton>
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
};

export { RateCard2 };
```

### `components/shadcnblocks/border-button.tsx`

```tsx
import { cn } from "cn";

import { Button } from "@/components/ui/button";

export type BorderCornerStyle = "l" | "plus" | "square" | "double" | "dot";

const cornerPlacements: Array<{ className: string }> = [
  { className: "absolute -bottom-px -left-px" },
  { className: "absolute -bottom-px -right-px scale-x-[-1]" },
  { className: "absolute -top-px -left-px scale-y-[-1]" },
  { className: "absolute -top-px -right-px scale-[-1]" },
];

export const BorderButton = ({
  children,
  variant = "outline",
  corners = "l",
  className,
}: {
  children: React.ReactNode;
  variant?:
    | "outline"
    | "default"
    | "destructive"
    | "secondary"
    | "ghost"
    | "link";
  corners?: BorderCornerStyle;
  className?: string;
}) => {
  return (
    <Button
      variant={variant}
      className={cn(
        className,
        "relative overflow-visible rounded-none px-4! shadow-none",
      )}
    >
      {children}
      {cornerPlacements.map((placement) => (
        <CornerArt
          key={placement.className}
          style={corners}
          className={placement.className}
        />
      ))}
    </Button>
  );
};

const overflowVisibleStyles: Partial<Record<BorderCornerStyle, boolean>> = {
  plus: true,
  double: true,
  dot: true,
};

const CornerArt = ({
  style,
  className,
}: {
  style: BorderCornerStyle;
  className?: string;
}) => {
  return (
    <svg
      viewBox="0 0 12 12"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      overflow={overflowVisibleStyles[style] ? "visible" : undefined}
      aria-hidden
      className={cn("pointer-events-none size-3", className)}
    >
      {cornerPaths[style]}
    </svg>
  );
};

const stroke = {
  stroke: "currentColor",
  strokeWidth: 1,
  strokeLinecap: "square" as const,
};

const cornerPaths: Record<BorderCornerStyle, React.ReactNode> = {
  l: (
    <>
      <path d="M0 0V12" {...stroke} />
      <path d="M0 12H12" {...stroke} />
    </>
  ),
  plus: (
    <>
      <path d="M-6 12 H6" {...stroke} />
      <path d="M0 6 V18" {...stroke} />
    </>
  ),
  square: <rect x="0" y="8" width="4" height="4" {...stroke} />,
  double: (
    <>
      <path d="M0 0 V12" {...stroke} />
      <path d="M0 12 H12" {...stroke} />
      <path d="M-2 2 V14" {...stroke} />
      <path d="M-2 14 H10" {...stroke} />
    </>
  ),
  dot: (
    <>
      <circle cx="0" cy="12" r="1.25" fill="currentColor" />
      <path d="M-4.5 12 H0" {...stroke} />
      <path d="M0 12 V16.5" {...stroke} />
    </>
  ),
};
```

### `hooks/use-google-font.ts`

```ts
import { useEffect } from "react";

export function useGoogleFont(fontName: string) {
  useEffect(() => {
    if (!fontName || typeof window === "undefined") return;

    const id = `google-font-${fontName.replace(/\s+/g, "-").toLowerCase()}`;
    if (document.getElementById(id)) return;

    const link = document.createElement("link");
    link.id = id;
    link.rel = "stylesheet";
    link.href = `https://fonts.googleapis.com/css2?family=${fontName.replace(
      /\s+/g,
      "+",
    )}&display=swap`;

    document.head.appendChild(link);

    return () => {
      const existingLink = document.getElementById(id);
      if (existingLink) {
        document.head.removeChild(existingLink);
      }
    };
  }, [fontName]);
}
```
