Glyph cardDesign system
Product

Glyph card

Cards with glyph headers or centered figures, plus status labels and links.

npx context-ds add glyph-card
In the product

Usage

Use it for

Cards on overview, usage and summary pages.

Avoid

  • Lucide icons in the card's header tile

Dependencies: glyph-tile, glyph-engine, @radix-ui/react-slot, lucide-react

Example

examples/glyph-card.tsx
"use client";import { useState } from "react";import { UsageSummaryDialog, CreditsRemaining, CreditsUsed } from "@/components/product/usage-summary";import { CardLink, CenteredCard, CenteredNumber, GlyphCard, StatusPill } from "@/components/product/glyph-card";export function Product() {  const [open, setOpen] = useState(false);  return (    <div className="grid w-full max-w-2xl gap-3 sm:grid-cols-2">      <GlyphCard glyph="monitors" title="Monitors" aside={<CardLink href="/preview/usage-card/Product">View all</CardLink>}>        <p className="text-caption text-fg-muted">12 pages watched, 3 changed this week.</p>      </GlyphCard>      <GlyphCard glyph="batches" title="Nightly crawl" aside={<StatusPill dotClassName="bg-success">Running</StatusPill>}>        <p className="text-caption text-fg-muted">1,204 of 2,000 pages done.</p>      </GlyphCard>      <div className="@container grid">        <CenteredCard label="API calls" badge="Last 30 days" visual={<CenteredNumber value="41,880" />} />      </div>      <div className="@container grid">        <CenteredCard label="Successful calls" badge="1,256 errors" visual={<CenteredNumber value="97.00" suffix="%" />} onClick={() => setOpen(true)} />      </div>      <UsageSummaryDialog open={open} onOpenChange={setOpen} description="Usage in this example."><CreditsRemaining remaining={41590} total={100000} /><CreditsUsed today={2318} month={58410} /></UsageSummaryDialog>    </div>  );}

Source

components/product/glyph-card.tsx
import * as React from "react";import { Slot } from "@radix-ui/react-slot";import { ArrowUpRight } from "lucide-react";import { StillGlyph } from "@/components/visuals/glyph/glyph-tile";import type { GlyphPairName } from "@/components/visuals/glyph/glyph-art";import { cn } from "@/lib/utils";// The redesigned dashboard's cards. GlyphCard heads a card with its page's dot glyph in a small bordered tile, the// title in body text and anything at the right (a link out, a switch, a status). CenteredCard holds one centered// visual (a big number) over its label, with a small pill in the corner. CardLink is the quiet way out of a card./** The up-right arrow every "go somewhere" link carries; it nudges in its direction on hover. */function LinkArrow({ className }: { className?: string }) {  return (    <ArrowUpRight      aria-hidden      className={cn("size-3.5 shrink-0 transition-[translate,color] duration-150 ease-emphasized group-hover:translate-x-px group-hover:-translate-y-px motion-reduce:transition-none motion-reduce:translate-none", className)}      strokeWidth={1.5}    />  );}/** Gray caption text with the arrow, darkening and underlining on hover. Pass your router's link with asChild. */function CardLink({ asChild = false, className, children, ...props }: React.ComponentProps<"a"> & { asChild?: boolean }) {  const classes = cn(    "group flex h-6 shrink-0 items-center gap-1 text-caption whitespace-nowrap text-fg-muted decoration-fg/25 underline-offset-4 outline-none transition-colors duration-150 hover:text-fg hover:underline focus-visible:ring-2 focus-visible:ring-focus-ring",    className,  );  if (asChild && React.isValidElement<{ children?: React.ReactNode }>(children)) {    return (      <Slot data-slot="card-link" className={classes} {...(props as React.ComponentProps<typeof Slot>)}>        {React.cloneElement(          children,          undefined,          <>            {children.props.children}            <LinkArrow />          </>,        )}      </Slot>    );  }  return (    <a data-slot="card-link" className={classes} {...props}>      {children}      <LinkArrow />    </a>  );}function GlyphCard({ glyph, title, aside, className, children }: { glyph: GlyphPairName; title: React.ReactNode; aside?: React.ReactNode; className?: string; children: React.ReactNode }) {  return (    <section data-slot="glyph-card" className={cn("flex min-w-0 flex-col rounded-card bg-surface p-3 shadow-card", className)}>      <div className="mb-2 flex items-center gap-2.5">        <span className="flex size-7 shrink-0 items-center justify-center rounded-tag border border-line">          <StillGlyph name={glyph} dense className="size-4 text-brand" />        </span>        <h3 className="flex-1 text-body-sm font-normal tracking-normal">{title}</h3>        {aside}      </div>      {children}    </section>  );}/** A small outlined pill for a card's corner or header: a state, with its colored dot. */function StatusPill({ dotClassName, className, children }: { dotClassName?: string; className?: string; children: React.ReactNode }) {  return (    <span className={cn("flex h-5 shrink-0 items-center gap-1.5 rounded-tag border border-line px-2 text-caption text-fg-muted", className)}>      {dotClassName && <span className={cn("size-1.5 rounded-pill", dotClassName)} aria-hidden />}      {children}    </span>  );}/** * One visual centered over its label, a pill in the top right. Given onClick (or a link through asChild) the whole card * is the action: the label turns brand blue and the card gains the translucent ring on hover. */function CenteredCard({ label, badge, visual, onClick, asChild = false, className, children }: { label: React.ReactNode; badge?: React.ReactNode; visual: React.ReactNode; onClick?: () => void; asChild?: boolean; className?: string; children?: React.ReactNode }) {  const interactive = asChild || Boolean(onClick);  // On a phone-width card the corner pill reaches over the visual, so the visual starts below it there.  const classes = cn(    "@container group relative isolate flex flex-col items-center overflow-hidden rounded-card bg-surface px-3 pt-9 pb-3 no-underline shadow-card outline-none transition-shadow duration-150 ease-out focus-visible:ring-2 focus-visible:ring-focus-ring @lg:pt-4",    interactive && "hover:shadow-[var(--ds-shadow-card),var(--ds-shadow-ring-hover)]",    className,  );  const body = (    <>      <span className="relative flex flex-1 items-center">{visual}</span>      <span className={cn("relative mt-2 text-body-sm text-fg", interactive && "transition-colors duration-150 group-hover:text-fg-brand")}>{label}</span>      {badge && <span className="absolute top-2.5 right-2.5 flex h-5 items-center rounded-tag border border-line bg-surface px-2 text-caption text-fg-muted">{badge}</span>}    </>  );  if (asChild && React.isValidElement(children)) {    return (      <Slot data-slot="centered-card" className={classes}>        {React.cloneElement(children, undefined, body)}      </Slot>    );  }  if (onClick)    return (      <button type="button" data-slot="centered-card" onClick={onClick} className={classes}>        {body}      </button>    );  return (    <div data-slot="centered-card" className={classes}>      {body}    </div>  );}/** A centered card's big number: 26px medium in brand blue, a unit after it, nudged toward its label. */function CenteredNumber({ value, suffix }: { value: React.ReactNode; suffix?: React.ReactNode }) {  return (    <span className="flex translate-y-1.5 items-baseline text-caption leading-none font-medium tracking-tight tabular-nums">      <span className="text-metric text-fg-brand">{value}</span>      {suffix && <span className="text-body font-normal tracking-normal text-fg-subtle">{suffix}</span>}    </span>  );}export { CardLink, CenteredCard, CenteredNumber, GlyphCard, LinkArrow, StatusPill };

Reference: context-dashboard c0ddf7a src/components/overview/overview-cards.tsx.