Kosmesis
Components

Ticket

An event/boarding ticket, either a paper stub or a modern boarding-pass card. Purely presentational — no Morphos equivalent.

Installation

npx kosmesis add ticket
pnpm dlx kosmesis add ticket
yarn dlx kosmesis add ticket
bunx kosmesis add ticket

Install the following dependencies:

npm install @morphos/icons
pnpm add @morphos/icons
yarn add @morphos/icons
bun add @morphos/icons

Copy and paste the following code into your project.

ticket.tsx
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import { Icon } from "@morphos/icons";import { cn } from "@/lib/utils";export type TicketKind = "stub" | "boarding-pass" | "raffle";export type TicketVariant = "physical" | "modern";export interface TicketDetail {  label: string;  value: string;}export interface TicketProps {  kind?: TicketKind;  variant?: TicketVariant;  title: string;  subtitle?: string;  date: string;  time?: string;  code: string;  details?: TicketDetail[];  /** Origin/destination airport codes — `"boarding-pass"` only. */  origin?: string;  destination?: string;  class?: string;}@Component()export class Ticket extends StatelessComponent<TicketProps> {  private renderDetailGrid(physical: boolean) {    const { date, time, details } = this.props;    const label = physical ? "text-[10px] tracking-wide text-neutral-500 uppercase" : "text-[10px] tracking-wide text-muted-foreground uppercase";    const value = physical ? "font-mono text-sm font-semibold" : "text-sm font-medium";    return (      <div class="mt-1 flex flex-wrap gap-4">        <div class="flex flex-col">          <span class={label}>Date</span>          <span class={value}>{date}</span>        </div>        {time && (          <div class="flex flex-col">            <span class={label}>Time</span>            <span class={value}>{time}</span>          </div>        )}        {details?.map((detail, i) => (          <div key={`${detail.label}-${String(i)}`} class="flex flex-col">            <span class={label}>{detail.label}</span>            <span class={value}>{detail.value}</span>          </div>        ))}      </div>    );  }  private renderRouteHero(physical: boolean) {    const { origin, destination } = this.props;    return (      <div class={cn("flex items-center gap-3 font-mono font-bold", physical ? "text-2xl text-neutral-900" : "text-2xl text-foreground")}>        <span>{origin}</span>        <Icon name="Plane" size={18} class={physical ? "text-neutral-400" : "text-muted-foreground"} />        <span>{destination}</span>      </div>    );  }  private renderStub(physical: boolean) {    const { code } = this.props;    if (physical) {      return (        <div class="flex w-24 shrink-0 flex-col items-center justify-between gap-3 border-l border-dashed border-neutral-400 bg-amber-100/70 p-3">          <Icon name="Ticket" size={14} class="mt-1 text-neutral-500" />          <div class="h-16 w-full bg-[repeating-linear-gradient(90deg,black_0px,black_2px,transparent_2px,transparent_4px)]" />          <span class="font-mono text-[10px] tracking-widest text-neutral-600">{code}</span>        </div>      );    }    return (      <div class="flex w-24 shrink-0 flex-col items-center justify-center gap-2 border-l border-dashed border-border bg-muted/40 p-3">        <Icon name="QrCode" size={28} class="text-foreground" />        <span class="font-mono text-[10px] tracking-widest text-muted-foreground">{code}</span>      </div>    );  }  private renderStubBody(physical: boolean) {    const { kind, title, subtitle } = this.props;    return (      <div class="flex flex-1 flex-col gap-3 p-5">        {kind === "boarding-pass" ? (          this.renderRouteHero(physical)        ) : physical ? (          <span class="text-base font-bold">{title}</span>        ) : (          <div class="flex items-center gap-2">            <span class="flex size-8 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">              <Icon name="Ticket" size={16} />            </span>            <span class="text-base font-semibold">{title}</span>          </div>        )}        {kind === "boarding-pass" && <span class={cn("font-medium", physical ? "text-sm text-neutral-700" : "text-sm text-foreground")}>{title}</span>}        {subtitle && <span class={physical ? "text-xs text-neutral-600" : "text-sm text-muted-foreground"}>{subtitle}</span>}        {this.renderDetailGrid(physical)}      </div>    );  }  private renderStubLayout(physical: boolean) {    const { class: cls } = this.props;    return (      <div        data-slot="ticket"        data-variant={physical ? "physical" : "modern"}        class={cn(          "relative flex w-full max-w-md",          physical ? "bg-amber-50 text-neutral-900 shadow-md" : "overflow-hidden rounded-2xl border bg-card text-card-foreground shadow-xs",          cls,        )}      >        {this.renderStubBody(physical)}        {this.renderStub(physical)}        {physical && (          <>            <div class="absolute -top-3 right-24 h-6 w-6 translate-x-1/2 rounded-full bg-background" />            <div class="absolute -bottom-3 right-24 h-6 w-6 translate-x-1/2 rounded-full bg-background" />          </>        )}      </div>    );  }  private renderRaffleLayout(physical: boolean) {    const { title, subtitle, date, code, class: cls } = this.props;    return (      <div        data-slot="ticket"        data-variant={physical ? "physical" : "modern"}        class={cn(          "relative mx-auto flex w-60 flex-col",          physical ? "bg-amber-50 text-neutral-900 shadow-md" : "overflow-hidden rounded-2xl border bg-card text-card-foreground shadow-xs",          cls,        )}      >        <div class="flex flex-col items-center gap-1 px-4 pt-5 pb-6 text-center">          <span class={cn("font-semibold", physical ? "text-sm" : "text-sm text-foreground")}>{title}</span>          {subtitle && <span class={physical ? "text-xs text-neutral-600" : "text-xs text-muted-foreground"}>{subtitle}</span>}          <span class={cn("mt-2 font-mono text-2xl font-bold tracking-wider", !physical && "text-foreground")}>{code}</span>          <span class={physical ? "text-[11px] text-neutral-500" : "text-[11px] text-muted-foreground"}>{date}</span>        </div>        <div class={cn("relative border-t border-dashed", physical ? "border-neutral-400" : "border-border")}>          {physical && (            <>              <div class="absolute top-1/2 -left-3 h-6 w-6 -translate-y-1/2 rounded-full bg-background" />              <div class="absolute top-1/2 -right-3 h-6 w-6 -translate-y-1/2 rounded-full bg-background" />            </>          )}        </div>        <div class={cn("flex items-center justify-center px-4 py-3", physical ? "bg-amber-100/70" : "bg-muted/40")}>          <span class={cn("font-mono text-sm font-bold tracking-wider", physical ? "text-neutral-700" : "text-muted-foreground")}>{`No. ${code}`}</span>        </div>      </div>    );  }  render() {    const { kind = "stub", variant } = this.props;    const physical = variant === "physical";    return kind === "raffle" ? this.renderRaffleLayout(physical) : this.renderStubLayout(physical);  }}

Install the following dependencies:

npm install @praxisjs/css @morphos/icons
pnpm add @praxisjs/css @morphos/icons
yarn add @praxisjs/css @morphos/icons
bun add @praxisjs/css @morphos/icons

Copy and paste the following code into your project.

ticket.tsx
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import { Icon } from "@morphos/icons";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class TicketStyles extends Stylesheet {  $stubShellPhysical = this.css({    position: "relative",    display: "flex",    width: "100%",    maxWidth: "28rem",    backgroundColor: "oklch(0.98 0.02 85)",    color: "oklch(0.2 0 0)",    boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",  });  $stubShellModern = this.css({    display: "flex",    width: "100%",    maxWidth: "28rem",    overflow: "hidden",    borderRadius: "1rem",    border: `1px solid ${t.border}`,    backgroundColor: t.card,    color: t.cardForeground,    boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",  });  $main = this.css({ display: "flex", flex: "1 1 0%", flexDirection: "column", gap: "0.75rem", padding: "1.25rem" });  $titlePhysical = this.css({ fontSize: "1rem", fontWeight: 700 });  $subtitlePhysical = this.css({ fontSize: "0.75rem", color: "oklch(0.45 0 0)" });  $headerModernRow = this.css({ display: "flex", alignItems: "center", gap: "0.5rem" });  $icon = this.css({    display: "flex",    height: "2rem",    width: "2rem",    flexShrink: 0,    alignItems: "center",    justifyContent: "center",    borderRadius: "9999px",    backgroundColor: `color-mix(in oklab, ${t.primary} 10%, transparent)`,    color: t.primary,  });  $titleModern = this.css({ fontSize: "1rem", fontWeight: 600 });  $subtitleModern = this.css({ fontSize: "0.875rem", color: t.mutedForeground });  $flightNumberPhysical = this.css({ fontSize: "0.875rem", fontWeight: 500, color: "oklch(0.35 0 0)" });  $flightNumberModern = this.css({ fontSize: "0.875rem", fontWeight: 500, color: t.foreground });  $routeHero = this.css({ display: "flex", alignItems: "center", gap: "0.75rem", fontFamily: "ui-monospace, monospace", fontWeight: 700, fontSize: "1.5rem" });  $routeHeroPhysical = this.css({ color: "oklch(0.2 0 0)" });  $routeHeroModern = this.css({ color: t.foreground });  $planeIconPhysical = this.css({ color: "oklch(0.65 0 0)" });  $planeIconModern = this.css({ color: t.mutedForeground });  $detailRow = this.css({ marginTop: "0.25rem", display: "flex", flexWrap: "wrap", gap: "1rem" });  $detail = this.css({ display: "flex", flexDirection: "column" });  $detailLabelPhysical = this.css({ fontSize: "10px", letterSpacing: "0.05em", color: "oklch(0.55 0 0)", textTransform: "uppercase" });  $detailLabelModern = this.css({ fontSize: "10px", letterSpacing: "0.05em", color: t.mutedForeground, textTransform: "uppercase" });  $detailValuePhysical = this.css({ fontFamily: "ui-monospace, monospace", fontSize: "0.875rem", fontWeight: 600 });  $detailValueModern = this.css({ fontSize: "0.875rem", fontWeight: 500 });  $stubPhysical = this.css({    display: "flex",    width: "6rem",    flexShrink: 0,    flexDirection: "column",    alignItems: "center",    justifyContent: "space-between",    gap: "0.75rem",    borderLeft: "1px dashed oklch(0.7 0.02 85)",    backgroundColor: "oklch(0.94 0.03 85)",    padding: "0.75rem",  });  $stubModern = this.css({    display: "flex",    width: "6rem",    flexShrink: 0,    flexDirection: "column",    alignItems: "center",    justifyContent: "center",    gap: "0.5rem",    borderLeft: `1px dashed ${t.border}`,    backgroundColor: `color-mix(in oklab, ${t.muted} 40%, transparent)`,    padding: "0.75rem",  });  $stubIcon = this.css({ marginTop: "0.25rem", color: "oklch(0.55 0 0)" });  $barcode = this.css({    height: "4rem",    width: "100%",    backgroundImage: "repeating-linear-gradient(90deg, black 0px, black 2px, transparent 2px, transparent 4px)",  });  $codePhysical = this.css({ fontFamily: "ui-monospace, monospace", fontSize: "10px", letterSpacing: "0.15em", color: "oklch(0.45 0 0)" });  $codeModern = this.css({ fontFamily: "ui-monospace, monospace", fontSize: "10px", letterSpacing: "0.15em", color: t.mutedForeground });  $notchTop = this.css({    position: "absolute",    top: "-0.75rem",    right: "6rem",    height: "1.5rem",    width: "1.5rem",    transform: "translateX(50%)",    borderRadius: "9999px",    backgroundColor: t.background,  });  $notchBottom = this.css({    position: "absolute",    bottom: "-0.75rem",    right: "6rem",    height: "1.5rem",    width: "1.5rem",    transform: "translateX(50%)",    borderRadius: "9999px",    backgroundColor: t.background,  });  $raffleShellPhysical = this.css({    position: "relative",    margin: "0 auto",    display: "flex",    width: "15rem",    flexDirection: "column",    backgroundColor: "oklch(0.98 0.02 85)",    color: "oklch(0.2 0 0)",    boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",  });  $raffleShellModern = this.css({    position: "relative",    margin: "0 auto",    display: "flex",    width: "15rem",    flexDirection: "column",    overflow: "hidden",    borderRadius: "1rem",    border: `1px solid ${t.border}`,    backgroundColor: t.card,    color: t.cardForeground,    boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",  });  $raffleTop = this.css({    display: "flex",    flexDirection: "column",    alignItems: "center",    gap: "0.25rem",    padding: "1.25rem 1rem 1.5rem",    textAlign: "center",  });  $raffleTitlePhysical = this.css({ fontSize: "0.875rem", fontWeight: 600 });  $raffleTitleModern = this.css({ fontSize: "0.875rem", fontWeight: 600, color: t.foreground });  $raffleSubtitlePhysical = this.css({ fontSize: "0.75rem", color: "oklch(0.45 0 0)" });  $raffleSubtitleModern = this.css({ fontSize: "0.75rem", color: t.mutedForeground });  $raffleCode = this.css({ marginTop: "0.5rem", fontFamily: "ui-monospace, monospace", fontSize: "1.5rem", fontWeight: 700, letterSpacing: "0.05em" });  $raffleCodeModern = this.css({ color: t.foreground });  $raffleDatePhysical = this.css({ fontSize: "11px", color: "oklch(0.55 0 0)" });  $raffleDateModern = this.css({ fontSize: "11px", color: t.mutedForeground });  $raffleSeamPhysical = this.css({ position: "relative", borderTop: "1px dashed oklch(0.7 0.02 85)" });  $raffleSeamModern = this.css({ position: "relative", borderTop: `1px dashed ${t.border}` });  $notchLeft = this.css({    position: "absolute",    top: "50%",    left: "-0.75rem",    height: "1.5rem",    width: "1.5rem",    transform: "translateY(-50%)",    borderRadius: "9999px",    backgroundColor: t.background,  });  $notchRight = this.css({    position: "absolute",    top: "50%",    right: "-0.75rem",    height: "1.5rem",    width: "1.5rem",    transform: "translateY(-50%)",    borderRadius: "9999px",    backgroundColor: t.background,  });  $raffleStubPhysical = this.css({    display: "flex",    alignItems: "center",    justifyContent: "center",    backgroundColor: "oklch(0.94 0.03 85)",    padding: "0.75rem 1rem",  });  $raffleStubModern = this.css({    display: "flex",    alignItems: "center",    justifyContent: "center",    backgroundColor: `color-mix(in oklab, ${t.muted} 40%, transparent)`,    padding: "0.75rem 1rem",  });  $raffleStubTextPhysical = this.css({ fontFamily: "ui-monospace, monospace", fontSize: "0.875rem", fontWeight: 700, letterSpacing: "0.05em", color: "oklch(0.35 0 0)" });  $raffleStubTextModern = this.css({ fontFamily: "ui-monospace, monospace", fontSize: "0.875rem", fontWeight: 700, letterSpacing: "0.05em", color: t.mutedForeground });}export type TicketKind = "stub" | "boarding-pass" | "raffle";export type TicketVariant = "physical" | "modern";export interface TicketDetail {  label: string;  value: string;}export interface TicketProps {  kind?: TicketKind;  variant?: TicketVariant;  title: string;  subtitle?: string;  date: string;  time?: string;  code: string;  details?: TicketDetail[];  /** Origin/destination airport codes — `"boarding-pass"` only. */  origin?: string;  destination?: string;  class?: string;}@Component()export class Ticket extends StatelessComponent<TicketProps> {  @Styled(TicketStyles) $s!: TicketStyles;  private renderDetailGrid(physical: boolean) {    const { date, time, details } = this.props;    const labelClass = physical ? this.$s.$detailLabelPhysical : this.$s.$detailLabelModern;    const valueClass = physical ? this.$s.$detailValuePhysical : this.$s.$detailValueModern;    return (      <div class={this.$s.$detailRow}>        <div class={this.$s.$detail}>          <span class={labelClass}>Date</span>          <span class={valueClass}>{date}</span>        </div>        {time && (          <div class={this.$s.$detail}>            <span class={labelClass}>Time</span>            <span class={valueClass}>{time}</span>          </div>        )}        {details?.map((detail, i) => (          <div key={`${detail.label}-${String(i)}`} class={this.$s.$detail}>            <span class={labelClass}>{detail.label}</span>            <span class={valueClass}>{detail.value}</span>          </div>        ))}      </div>    );  }  private renderRouteHero(physical: boolean) {    const { origin, destination } = this.props;    return (      <div class={cx(this.$s.$routeHero, physical ? this.$s.$routeHeroPhysical : this.$s.$routeHeroModern)}>        <span>{origin}</span>        <Icon name="Plane" size={18} class={physical ? this.$s.$planeIconPhysical : this.$s.$planeIconModern} />        <span>{destination}</span>      </div>    );  }  private renderStub(physical: boolean) {    const { code } = this.props;    if (physical) {      return (        <div class={this.$s.$stubPhysical}>          <Icon name="Ticket" size={14} class={this.$s.$stubIcon} />          <div class={this.$s.$barcode} />          <span class={this.$s.$codePhysical}>{code}</span>        </div>      );    }    return (      <div class={this.$s.$stubModern}>        <Icon name="QrCode" size={28} />        <span class={this.$s.$codeModern}>{code}</span>      </div>    );  }  private renderStubBody(physical: boolean) {    const { kind, title, subtitle } = this.props;    return (      <div class={this.$s.$main}>        {kind === "boarding-pass" ? (          this.renderRouteHero(physical)        ) : physical ? (          <span class={this.$s.$titlePhysical}>{title}</span>        ) : (          <div class={this.$s.$headerModernRow}>            <span class={this.$s.$icon}>              <Icon name="Ticket" size={16} />            </span>            <span class={this.$s.$titleModern}>{title}</span>          </div>        )}        {kind === "boarding-pass" && <span class={physical ? this.$s.$flightNumberPhysical : this.$s.$flightNumberModern}>{title}</span>}        {subtitle && <span class={physical ? this.$s.$subtitlePhysical : this.$s.$subtitleModern}>{subtitle}</span>}        {this.renderDetailGrid(physical)}      </div>    );  }  private renderStubLayout(physical: boolean) {    const { class: cls } = this.props;    return (      <div data-slot="ticket" data-variant={physical ? "physical" : "modern"} class={cx(physical ? this.$s.$stubShellPhysical : this.$s.$stubShellModern, cls)}>        {this.renderStubBody(physical)}        {this.renderStub(physical)}        {physical && (          <>            <div class={this.$s.$notchTop} />            <div class={this.$s.$notchBottom} />          </>        )}      </div>    );  }  private renderRaffleLayout(physical: boolean) {    const { title, subtitle, date, code, class: cls } = this.props;    return (      <div data-slot="ticket" data-variant={physical ? "physical" : "modern"} class={cx(physical ? this.$s.$raffleShellPhysical : this.$s.$raffleShellModern, cls)}>        <div class={this.$s.$raffleTop}>          <span class={physical ? this.$s.$raffleTitlePhysical : this.$s.$raffleTitleModern}>{title}</span>          {subtitle && <span class={physical ? this.$s.$raffleSubtitlePhysical : this.$s.$raffleSubtitleModern}>{subtitle}</span>}          <span class={cx(this.$s.$raffleCode, !physical && this.$s.$raffleCodeModern)}>{code}</span>          <span class={physical ? this.$s.$raffleDatePhysical : this.$s.$raffleDateModern}>{date}</span>        </div>        <div class={physical ? this.$s.$raffleSeamPhysical : this.$s.$raffleSeamModern}>          {physical && (            <>              <div class={this.$s.$notchLeft} />              <div class={this.$s.$notchRight} />            </>          )}        </div>        <div class={physical ? this.$s.$raffleStubPhysical : this.$s.$raffleStubModern}>          <span class={physical ? this.$s.$raffleStubTextPhysical : this.$s.$raffleStubTextModern}>{`No. ${code}`}</span>        </div>      </div>    );  }  render() {    const { kind = "stub", variant } = this.props;    const physical = variant === "physical";    return kind === "raffle" ? this.renderRaffleLayout(physical) : this.renderStubLayout(physical);  }}

Examples

Physical

Kind: Boarding Pass

Kind: Raffle

About

Purely presentational — no Morphos equivalent. Two independent props control the result:

  • variant swaps the chrome rather than just colors:
    • "modern" (default) — a theme-aware boarding-pass card built from the same tokens as the rest of the registry, with a plain dashed divider separating the main stub from a QR-code stub.
    • "physical" — a paper ticket. It uses fixed cardstock colors instead of theme tokens and punches circular notches through the edge exactly where the dashed perforation meets it — the classic torn-stub silhouette. The notches are circles colored with the background token/class, so they read correctly as long as the ticket sits on the page background, the same assumption every card-on-canvas surface in this registry already makes.
  • kind swaps the layout:
    • "stub" (default) — a general-admission ticket: main content on the left, a barcode/QR stub on the right, seam running top-to-bottom.
    • "boarding-pass" — the same shell, but the header is replaced by an origin/destination route hero with a plane icon between the two airport codes.
    • "raffle" — a narrow ticket split vertically instead of horizontally: notches on the left/right of a single dashed seam, the same code printed on both the kept half and the drawn stub below it.

details is an open-ended list of extra label/value pairs (seat, gate, section, ...) rendered alongside the built-in date/time fields. origin/destination only apply to kind="boarding-pass".

Usage

import { Ticket } from "@/components/ui/ticket";

<Ticket
  title="Signal & Noise"
  subtitle="The Grand Hall, Lisbon"
  date="Aug 03, 2026"
  time="20:00"
  code="KSM-88213"
  details={[
    { label: "Seat", value: "12A" },
    { label: "Gate", value: "B7" },
  ]}
/>

// Boarding pass — same shell, a route hero instead of a plain title
<Ticket
  kind="boarding-pass"
  origin="LIS"
  destination="JFK"
  title="TAP 1954 · Ada Lovelace"
  date="Aug 03, 2026"
  time="Boarding 19:40"
  code="KSM-88213"
  details={[{ label: "Gate", value: "22" }, { label: "Seat", value: "14C" }]}
/>

// Raffle — split vertically instead of horizontally
<Ticket variant="physical" kind="raffle" title="Summer Raffle" subtitle="Kosmesis Café" date="Aug 03, 2026" code="0427" />

Props

PropTypeDefault
titlestring
subtitlestring
datestring
timestring
codestring
details{ label: string; value: string }[]
originstring
destinationstring
kind"stub" | "boarding-pass" | "raffle""stub"
variant"physical" | "modern""modern"
classstring

On this page