Skip to content

Components

@memoturn/ui is the shared component library used by the Memoturn dashboard and this docs site. It's built on shadcn/ui new-york-v4, Radix UI primitives, Tailwind CSS v4, and a brand color overlay defined in tokens.css.

The library is workspace-internal today ("private": true). There's no npm release.

The 57 component primitives are sourced verbatim from shadcn/ui's new-york-v4 registry. Brand identity comes through three vectors:

  1. --primary: brand teal (lagoon-deep light / lagoon dark) appears on every button, focus ring, accent, and active state.
  2. Display typography: one Archivo family, with the width axis (font-stretch: 110%) rather than a second typeface separating display from body.
  3. Atmospheric decoration: .atoll-band topographic texture appears on hero/CTA/empty surfaces; brand gradients (--gradient-lagoon, --gradient-atoll) on hero badges and the user avatar.

Structural surfaces (--background, --card, --popover, --border, --muted, --accent) are dark-first: :root carries the sea-ink-tinted instrument panel (--background: oklch(0.132 0.004 215), never pure #000) and [data-theme="light"], .light is the reading-mode override. Borders and rules are foam tints on dark, sea-ink tints on light. The docs landing (HeroCard, LinkChip, FeatureCard, CTA) uses --docs-panel-bg (sand-tinted page-white in light, card-tinted in dark) with hairline --docs-line borders; the closing-band CTA lights up the brand --gradient-atoll as the page's single brand moment.

Defined in packages/ui/src/styles/tokens.css. Composed via Tailwind v4 @theme inline so every token is addressable as a Tailwind utility (bg-card, text-primary, border-input, etc.).

TokenLightDarkUse
--lagoon#4fb8b2#60d7cfBright accent (bg-lagoon)
--lagoon-deep#328f97#8de5dbDeep accent (text-lagoon-deep)
--palm#2f6a4a#6ec89aSecondary accent / success
--sand / --foamwarm palesdark scaledAtmospheric tints
--accent-warm#e89456#f5b169Warm accent (warnings, CTA highlights)

These exist for hero/CTA decoration. Don't reach for them in primitives. Primitives consume the semantic tokens below, which already route through --primary etc.

FamilyTokens
Surfaces--background, --card, --popover, --surface (muted panels), --sidebar
Text--foreground, --card-foreground, --muted-foreground, --popover-foreground
Action--primary (brand teal), --secondary, --accent, --destructive, --success, --warning
Form--border, --input, --ring (also brand teal)
Code--code, --code-foreground, --code-highlight, --code-number
Chart--chart-1 through --chart-5 (brand-sequenced)
Selection--selection, --selection-foreground
Overlay--overlay, --overlay-strong, --overlay-subtle (three-tier)

Every token has a dark counterpart that flips on :root[data-theme="dark"], .dark class, or @media (prefers-color-scheme: dark).

--radius defaults to 0.375rem (~6px). Derived scale: --radius-sm through --radius-4xl via calc(var(--radius) * N). Use Tailwind utilities: rounded-sm, rounded-md, rounded-lg, rounded-xl.

Resting surfaces carry no shadow — depth comes from hairline rules, the canvas → panel tonal step, and --glow-lagoon, a static radial lagoon wash falling from a surface's top edge. shadow-md / shadow-lg remain for overlays (modal, popover, sheet, tooltip, dropdown), where elevation is the meaning.

  • Body / UI / display: Archivo (variable; weight 400–800, width 100–125), --font-sans / --font-heading
  • Code: system mono stack, --font-mono / font-mono

One variable family carries the whole surface. At normal width Archivo is body and UI; at font-stretch: 110% with -0.03em tracking and weight 700 it becomes the display voice — compact, geometric, machine-set. A second sans, a serif, or italics is a flag to revise.

Four named sizes carry the display scale, each bundling size, leading, tracking and weight, so a heading needs one utility rather than an inline clamp() repeated at every call site:

StepSizeUse
text-heroclamp(2.75rem, 6.5vw, 5.25rem)The one h1 on a marketing page.
text-displayclamp(2rem, 4vw, 3rem)Major section openers.
text-sectionclamp(1.875rem, 3.8vw, 2.875rem)Ordinary section headings.
text-bandclamp(1.875rem, 4.5vw, 3.5rem)Headline inside a full-bleed band.

Tracking rides the size — the larger the step, the tighter it is set, so the scale sits on one optical curve rather than sharing a flat value. -0.04em remains the floor.

Pair a step with .display-title, which carries the family and the width axis (font-stretch has no Tailwind sub-token slot, so it cannot live in the theme): class="display-title text-section text-foreground".

Two utility classes give you the brand voice:

  • .kicker: small uppercase tracked label (KICKER LIKE THIS). Composes with margin/color modifiers: class="kicker mb-2 text-primary".
  • .display-title: Archivo 700 at 110% width, -0.03em tracking, 1.04 line-height, text-wrap: balance. Use for hero/section/card titles. Defined in @layer components, so any utility written beside it — a text-* step, tracking-tight — wins. Unlayered it beat every utility silently, which discarded overrides several call sites already asked for.
  • .tone-soft: --muted-foreground. The recessive half of a two-tone headline — the section-header system that replaced uppercase eyebrows.
  • .frame-shot / .glow-panel / .lit-edge: the capture frame, a cell that lights on hover, and a centre-anchored lagoon hairline on a section boundary.
  • .rule-gradient: a 1px frame painted as a gradient and masked so only the border survives, giving a panel a lit top lip with no shadow and no hue. Where .lit-edge lights one horizontal seam in lagoon, this frames a panel in ink. Apply it through <RuleEdge />, never directly: mask composites an element and its descendants, so the bare class on a panel clips that panel's own text away.
  • .panel-noise: sub-perceptual feTurbulence grain at 3.5%, inline as a data URI. A flat OKLCH fill this dark bands across a wide surface; the grain breaks it up and gives the surface tooth without touching text contrast.

These define the dashboard's visual rhythm without touching component primitives:

  • .page-wrap: fluid full-width container, soft cap at 1800px, padding scales clamp(1rem, 3vw, 3rem).
  • .atoll-band: topographic concentric-arc texture at 6% opacity with a radial mask; compose on cards or sections for ambient depth.
  • .rise-in: entrance animation; respects prefers-reduced-motion.
  • .shimmer: left-to-right brand-tinted gradient sweep; powers the Skeleton primitive.
  • .nav-link: underline indicator that grows from left on hover/active; brand-teal stroke.

All 57 shadcn new-york-v4 components are exported from @memoturn/ui. Categories below highlight the most-used; the full list is in packages/ui/src/index.ts.

Button, Input, Textarea, Select, NativeSelect, Checkbox, RadioGroup, Switch, Slider, Label, Form (react-hook-form integration), Field, FieldGroup, InputGroup, InputOTP, Combobox, Toggle, ToggleGroup, ButtonGroup.

Every form control shares one focus language: ring-2 ring-ring on focus, hover:border-ring/40 preview, brand-teal ring (because --ring resolves to --primary). The controls that carry a solid --primary fill when active (Button, Checkbox, Switch) add ring-offset-2 ring-offset-background: a ring drawn straight onto that fill measures 1:1 against it and vanishes at exactly the moment the control is on.

Dialog, AlertDialog, Sheet, Drawer, Popover, HoverCard, Tooltip, DropdownMenu, ContextMenu, Menubar, Command (CommandDialog for ⌘K palettes).

All use bg-popover for content and the brand-tinted overlay scrim. data-slot attributes on every element for inspect-and-style.

Card (+ CardHeader/CardTitle/CardDescription/CardContent/CardFooter/CardAction), Sidebar, Resizable panels, ScrollArea, Separator, AspectRatio, Tabs.

CardTitle / DialogTitle / SheetTitle / DrawerTitle / EmptyTitle use font-heading (Archivo). Every modal display moment is in the brand voice.

Table, Badge, Avatar (+ AvatarFallback / AvatarBadge / AvatarGroup), Item (data row primitive), Empty (EmptyTitle/EmptyDescription/EmptyMedia), Pagination, Breadcrumb, Accordion, Collapsible, Calendar, Carousel, Chart (recharts wrapper), Progress, Skeleton, Spinner, Kbd.

Badge extends shadcn defaults with three brand variants (dense primary tint, lexical palm tint, hot warm tint) used for search-leg color coding. Skeleton uses .shimmer for brand-tinted loading.

Toaster (Sonner-backed) + the toast helper. Mount once at the app root:

import { Toaster } from "@memoturn/ui";
export function RootLayout() {
return (
<>
{/* …app… */}
<Toaster />
</>
);
}

Then fire from anywhere:

import { toast } from "@memoturn/ui";
await revokeKey(id);
toast.success(`Revoked "${name}"`);
// or with a description
toast.error({ title: "Revoke failed", description: err.message });

The helper exposes success, error, info, and dismiss. Each type gets a left-border color stripe (success → palm, info → primary, warning → accent-warm, error → destructive) and a brand-tinted icon.

Higher-level building blocks specific to the Memoturn brand voice. They compose primitives plus brand decoration (atoll-band, gradients, kicker labels).

ComponentSourceUse
HeroHero.tsxLanding / section hero with kicker, title, description, action slots.
FeatureCardFeatureCard.tsxIcon + kicker + title + description + footer; lifts on hover.
CTACTA.tsxBrand-gradient call-to-action panel with primary + secondary actions.
EmptyStateEmptyState.tsxCentered empty state with BrandMark, title, description, action.
CodeBlockCodeBlock.tsxSnippet block with copy-to-clipboard button.
LogoLogo.tsx4 variants: full, mark, wordmark, chip.
BrandMarkBrandMark.tsxStandalone SVG mark.
ThemeToggleThemeToggle.tsxLight / dark / auto switcher; persists to localStorage.
RuleEdgeRuleEdge.tsxThe decorative overlay that carries .rule-gradient. Parent needs relative.
SignalFieldSignalField.tsxThe hero's live texture: a 96px ruled grid in CSS with turns travelling it on a canvas above. Suspends off-screen, below 640px, and under prefers-reduced-motion, which paints the grid alone rather than a blank surface.

The dashboard wires apps/app/components.json so the shadcn CLI installs into packages/ui/src/components/:

Terminal window
pnpm exec shadcn add <component>

After install, normalize the imports to relative paths (@/lib/utils → ../lib/utils, @/components/ui/... → ./...) and re-export from packages/ui/src/index.ts.

Both the Memoturn dashboard (TanStack Start on Workers) and this docs site (Astro + Starlight) consume tokens.css directly. The dashboard mounts via Tailwind v4's @theme inline directive; the docs site @imports and adds back-compat aliases for older brand-tinted decoration.

To embed @memoturn/ui in a third app, import the tokens once at the top of your global stylesheet, then add the Tailwind layer:

@import "tailwindcss";
@import "tw-animate-css";
@import "@memoturn/ui/styles/tokens.css";
@import "@memoturn/ui/styles/base.css";
@source "../node_modules/@memoturn/ui/src/components/**/*.{ts,tsx}";
@custom-variant dark (&:is([data-theme="dark"] *, .dark *));
  • Every interactive primitive has a visible focus-visible ring driven by --ring (brand teal).
  • Radix-backed components inherit Radix's keyboard and screen-reader support.
  • Form inputs respect aria-invalid (destructive ring + border) and pair with <Label htmlFor>.
  • Empty and error boundaries use semantic headings; modal titles use proper Dialog.Title.
  • Reduced motion: .rise-in and .shimmer honor prefers-reduced-motion: reduce.

If you find a component that fails an axe-core check, please open an issue on GitHub. Accessibility regressions are bugs, not enhancements.