nextui.dev
Home
Library
Docs
Pricing
Components26
AI & agents5
  • AI chat inputPro
  • Agent task checklistPro
  • Agent thinking
  • Thinking indicator
  • Prompt deckNew
Forms & inputs7
  • Input with label
  • Input with icon and button
  • Settings switches
  • Password with strength meter
  • One-time code input
  • WaitlistNew
  • File dropzonePro
Cards5
  • Style marquee cardPro
  • Folder card
  • Celebration cardPro
  • Smart PDF stackNew
  • Waitlist pass
Buttons, badges & avatars5
  • Buttons with icons
  • QR scan badge
  • Avatar group
  • Rolling price badge
  • Avatars with status
Feedback & display4
  • Alert banners
  • FAQ accordion
  • Keyboard shortcuts
  • Empty state
Blocks11
Hero4
  • Animated pill heroPro
  • Integrations grid hero
  • Waitlist heroNew
  • Orbit chat heroNew
Features & pricing3
  • Usage cardPro
  • Three-tier pricing
  • Feature with board
Testimonials & team3
  • Testimonial cards
  • Single quote
  • Team carousel
Sign-in1
  • Sign-in cardPro
Motion2
Effects2
  • Text revealPro
  • Sound ringNew
Themes9
  • Default
  • Ember
  • Ocean
  • Forest
  • Grape
  • Rose
  • Amber
  • Clay
  • Brutal
nextui.dev

nextui components, blocks and themes you install with one command.

Product

  • Components
  • Blocks
  • Motion
  • Themes
  • Pricing

Resources

  • Docs
  • Roadmap
  • GitHub

Legal

  • License
  • Privacy
  • Terms
  • Refunds

nextUI

[ 200 OK ] · nextUI

© 2026 NEXTUI.DEV. All rights reserved.

ComponentsCardsWaitlist pass

Waitlist pass

Ticket-style waitlist pass with notched edges, a QR code stub and a tilt-and-glare hover.

Copy prompt
MCP

Installation

Runs as is, no setup needed. Want a shorter command like @nextui/tabs-01? See short names.

$pnpm dlx shadcn@latest add https://tinistudio.vercel.app/r/tabs-01.json

Related

All components
Pro

Style marquee cardNew

Feature card with rows of style tags drifting in alternating directions behind faded edges, above a short headline.

Folder card

Folder that opens on hover and fans out the files inside.

Pro

Celebration card

Task bubbles rain down, pile up on the headline and drain away when you switch range.

npx shadcn@latest add https://tinistudio.vercel.app/r/tabs-01.json
components/nextui/tabs-01.tsx
"use client";

import type { CSSProperties, PointerEvent } from "react";
import {
  motion,
  useMotionTemplate,
  useMotionValue,
  useReducedMotion,
  useSpring,
  useTransform,
} from "motion/react";

const PASS = {
  brand: "nextui",
  label: "Waitlist pass",
  email: "alex@example.com",
  handle: "@alexmorgan",
};

// Where the stub ends and the tear line sits, plus the notch sizes.
const CUT = "36%";
const CORNER = 20;
const NOTCH = 14;

const hole = (at: string, r: number) => `radial-gradient(circle at ${at}, transparent ${r}px, #000 ${r + 0.5}px)`;
const MASK = [
  hole("0 0", CORNER),
  hole("100% 0", CORNER),
  hole("0 100%", CORNER),
  hole("100% 100%", CORNER),
  hole(`${CUT} 0`, NOTCH),
  hole(`${CUT} 100%`, NOTCH),
].join(", ");

const TICKET_STYLE: CSSProperties = {
  maskImage: MASK,
  WebkitMaskImage: MASK,
  maskComposite: "intersect",
  WebkitMaskComposite: "source-in",
  backgroundImage:
    "linear-gradient(105deg, color-mix(in oklab, var(--secondary) 95%, var(--foreground)), color-mix(in oklab, var(--secondary) 91%, var(--foreground)))",
};

// Sample QR code ("https://nextui.dev", version 2, 25x25), drawn as one path.
const QR = [
  "1111111010100101101111111",
  "1000001000110000101000001",
  "1011101000101001001011101",
  "1011101010100001101011101",
  "1011101011100011101011101",
  "1000001010101000001000001",
  "1111111010101010101111111",
  "0000000011101101000000000",
  "1000101111100100011111001",
  "1111110000000111100011010",
  "0001101111100001100011100",
  "1111010010111010000110110",
  "1110101000100111011101111",
  "1001000010001001010010010",
  "0000001110111111011111100",
  "0010010111110101110110110",
  "1111111111101101111111100",
  "0000000010000110100010000",
  "1111111010100000101010000",
  "1000001001001010100011111",
  "1011101010110110111111101",
  "1011101000101001011100111",
  "1011101001011110101001010",
  "1000001000010100000111110",
  "1111111010101101001000111",
];
const QR_PATH = QR.flatMap((row, y) =>
  [...row].map((bit, x) => (bit === "1" ? `M${x} ${y}h1v1h-1z` : "")),
).join("");

function Mark({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 20 20" aria-hidden className={className}>
      <rect x="1" y="1" width="8" height="8" rx="2" fill="currentColor" />
      <rect x="11" y="1" width="8" height="8" rx="2" fill="currentColor" opacity="0.55" />
      <rect x="1" y="11" width="8" height="8" rx="2" fill="currentColor" opacity="0.55" />
      <rect x="11" y="11" width="8" height="8" rx="2" fill="currentColor" opacity="0.25" />
    </svg>
  );
}

export default function Tabs01() {
  const reduce = useReducedMotion();

  // Pointer position across the ticket, 0..1 on each axis.
  const px = useMotionValue(0.5);
  const py = useMotionValue(0.5);
  const spring = { stiffness: 180, damping: 18, mass: 0.4 };
  const rotateX = useSpring(useTransform(py, [0, 1], [7, -7]), spring);
  const rotateY = useSpring(useTransform(px, [0, 1], [-10, 10]), spring);
  const glareX = useTransform(px, (v) => v * 100);
  const glareY = useTransform(py, (v) => v * 100);
  const glare = useMotionTemplate`radial-gradient(circle at ${glareX}% ${glareY}%, rgb(255 255 255 / 0.5), transparent 50%)`;
  const glareOpacity = useSpring(0, spring);

  const onMove = (e: PointerEvent<HTMLDivElement>) => {
    if (reduce) return;
    const r = e.currentTarget.getBoundingClientRect();
    px.set((e.clientX - r.left) / r.width);
    py.set((e.clientY - r.top) / r.height);
    glareOpacity.set(1);
  };
  const onLeave = () => {
    px.set(0.5);
    py.set(0.5);
    glareOpacity.set(0);
  };

  return (
    <div className="w-full max-w-xl p-4 [perspective:1100px]">
      <motion.div
        onPointerMove={onMove}
        onPointerLeave={onLeave}
        style={reduce ? undefined : { rotateX, rotateY, transformStyle: "preserve-3d" }}
        className="drop-shadow-[0_14px_24px_rgb(255_255_255/0.9)]"
      >
        <article
          aria-label={`${PASS.label} for ${PASS.email}`}
          style={TICKET_STYLE}
          className="@container relative grid aspect-[3/1] min-h-36 w-full grid-cols-[36%_1fr] overflow-hidden text-foreground"
        >
          {/* Stub: QR code */}
          <div className="flex items-center justify-center px-[calc(18px+12%)] py-[calc(16px+5%)]">
            <svg
              viewBox={`0 0 ${QR.length} ${QR.length}`}
              shapeRendering="crispEdges"
              aria-hidden
              className="h-full w-full"
            >
              <path d={QR_PATH} fill="currentColor" />
            </svg>
          </div>

          {/* Tear line */}
          <span
            aria-hidden
            className="absolute inset-y-[calc(14px+8%)] w-0.5 -translate-x-1/2 bg-[repeating-linear-gradient(to_bottom,currentColor_0_10px,transparent_10px_18px)] opacity-30"
            style={{ left: CUT }}
          />

          {/* Details */}
          <div className="flex min-w-0 flex-col justify-center gap-0.5 py-3 pl-[9%] pr-[10%]">
            <div className="mb-1.5 flex items-center gap-2 text-sm font-semibold tracking-tight @md:text-base">
              <Mark className="size-4" />
              {PASS.brand}
            </div>
            <p className="font-mono text-[11px] uppercase tracking-[0.2em] text-muted-foreground">{PASS.label}</p>
            <p className="truncate text-lg font-semibold tracking-tight @md:text-2xl">{PASS.email}</p>
            <p className="truncate text-sm text-muted-foreground">{PASS.handle}</p>
          </div>

          {/* Light that follows the pointer */}
          {!reduce && (
            <motion.span
              aria-hidden
              style={{ backgroundImage: glare, opacity: glareOpacity }}
              className="pointer-events-none absolute inset-0 mix-blend-overlay"
            />
          )}
        </article>
      </motion.div>
    </div>
  );
}
Sign in