ryme-ui
v0.2.6
Published
Ryme marketing-site SDK: full design system + UI components. Grainient, side lines, grid strips, section diamonds, layout (header/footer/section head), 45+ shadcn-style components, cn, useIsMobile.
Readme
ryme-ui
The official Ryme design-system SDK. One package reproduces the complete look, layout and component set of the Ryme marketing site — drop it into any React project and the entire design system comes with it:
- Visuals —
Grainient(WebGL animated gradient),SideLines,GridStrip,RuleGrid,GridPattern,SectionBorder - Layout —
SiteHeader,SiteFooter(router-agnostic vialinkComponent),SectionHead,RymeMark - UI — 45+ shadcn-style components (
Button,Card,Dialog,Sheet,Select,Sidebar,Chart,Calendar, …) pre-themed for the Ryme dark look - Utilities —
cn,useIsMobile - Design system — one self-contained CSS file with all Tailwind v4 tokens, custom utilities and animations
Table of contents
- Requirements
- Install
- Quick start
- Package structure
- Design system
- Components
- Guides
- Accessibility & performance
- Browser support
- Development
- Publishing
- Troubleshooting
- License
Requirements
- React
>= 18andreact-dom >= 18(peer dependencies) - Tailwind CSS v4 in your project (the stylesheet is built with Tailwind v4 syntax)
- WebGL 2 support for the
Grainientvisual (all modern browsers)
Everything else — Radix primitives, class-variance-authority, tailwind-merge, ogl,
lucide icons, Recharts, Sonner, etc. — is a declared dependency and installs automatically.
Install
# npm
npm install ryme-ui
# bun
bun add ryme-ui
# pnpm / yarn
pnpm add ryme-ui
yarn add ryme-uiThe package ships a pre-built dist (ESM + .d.ts). Because the published dist is
committed to npm, no build step runs on install in published versions. As a git dependency
the prepare script builds it automatically:
bun add github:Ryme-Labs/ryme-ui
npm install github:Ryme-Labs/ryme-uiQuick start
// main.tsx (or your app entry)
import "ryme-ui/styles.css";
// Any page:
import {
Button,
Grainient,
GridStrip,
RuleGrid,
SectionBorder,
SectionHead,
SiteFooter,
SiteHeader,
SideLines,
} from "ryme-ui";
import { Link } from "react-router"; // or any router — see linkComponent
export function Page() {
return (
<div className="flex min-h-screen flex-col">
<SiteHeader linkComponent={Link} />
<main className="flex-1">
<div className="relative">
<GridStrip side="left" edgeCut />
<GridStrip side="right" edgeCut />
<SideLines />
<section className="relative flex min-h-screen items-center overflow-hidden">
<div className="absolute inset-0">
<Grainient color1="#EF4444" color2="#B91C1C" color3="#000000" />
</div>
<div className="relative mx-auto max-w-6xl px-5 py-24 sm:px-8">
<SectionHead id="hero" label="Your label" title="Your headline." />
<Button asChild className="mt-8">
<Link to="/demo">Get started</Link>
</Button>
</div>
</section>
</div>
</main>
<SiteFooter noBorder linkComponent={Link} />
</div>
);
}Package structure
ryme-ui/
├── src/
│ ├── index.ts # public entry — everything is re-exported here
│ ├── ryme-ui.css # design system: tokens, utilities, visuals CSS
│ ├── cn.ts # cn() — clsx + tailwind-merge
│ ├── use-mobile.tsx # useIsMobile() — < 768px hook
│ ├── layout/ # SiteHeader, SiteFooter, SectionHead, RymeMark
│ ├── ui/ # 45+ shadcn-style components (Radix-based)
│ └── visuals/ # Grainient, GridStrip, RuleGrid, SectionBorder,
│ # SideLines, GridPattern
├── dist/ # build output (ESM + .d.ts + ryme-ui.css)
├── vite.config.ts # library build config
└── package.jsonImports are resolved from the package root:
import "ryme-ui/styles.css"; // the full design system CSS
import { Button, Grainient } from "ryme-ui"; // componentsDesign system
The entire Ryme look lives in one CSS file: ryme-ui/styles.css. It is a Tailwind v4
stylesheet (@import "tailwindcss") that defines the theme tokens, base layer, custom
utilities (label-mono, marquee) and every ryme-* class used by the visuals. Import it
once at your app root — do not import it per page.
Theme tokens
Colors are defined in :root as oklch values and exposed to Tailwind utilities via
@theme inline (so bg-primary, text-muted-foreground, border-border, … all work).
| Token | Default value | Used for |
| --- | --- | --- |
| --background | oklch(0.13 0.008 20) | Page background (near-black warm) |
| --foreground | oklch(0.94 0.004 60) | Primary text |
| --card | oklch(0.16 0.009 20) | Cards, form fields, panels |
| --card-foreground | oklch(0.94 0.004 60) | Text on cards |
| --popover / --popover-foreground | card values | Dropdowns, menus, sheets |
| --primary | oklch(0.57 0.21 26) | The Ryme red — accents, buttons, diamonds |
| --primary-foreground | oklch(0.98 0.005 60) | Text on primary |
| --secondary / --secondary-foreground | dark warm | Secondary surfaces |
| --muted | oklch(0.19 0.008 20) | Muted surfaces |
| --muted-foreground | oklch(0.66 0.012 30) | Secondary text, labels |
| --accent / --accent-foreground | warm red-tint | Hover states |
| --destructive | primary red | Destructive actions |
| --border | oklch(0.26 0.012 25) | All borders, hairlines |
| --input | border value | Form field borders |
| --ring | primary red | Focus rings |
| --chart-1 … --chart-5 | red/warm ramp | Chart component palettes |
| --sidebar* | background ramp | Sidebar component palette |
| --radius | 0.25rem | Global radius scale seed |
ryme-* visual tokens
The visuals read their own CSS custom properties so every color/position can be overridden per project:
| Token | Default | Used by |
| --- | --- | --- |
| --ryme-background | var(--background) | Grid strip edge-cut lines |
| --ryme-primary | var(--primary) | Section diamonds |
| --ryme-border | var(--border) | Side lines, section border line |
| --ryme-grid-line | oklch(0.22 0.01 25) | RuleGrid hairlines |
| --ryme-grid-color | rgba(156,163,175,0.15) | GridStrip grid |
| --ryme-grid-pattern-color | rgba(156,163,175,0.3) | GridPattern SVG |
| --glow-red | red glow shadow | Optional glow effects |
Fonts
- Sans:
Inter Tight(falls back to system-ui) - Mono:
JetBrains Mono(falls back to ui-monospace)
Mapped as --font-sans / --font-mono, so font-mono, font-sans utilities work. Load
them yourself (Google Fonts or self-hosted); the package does not fetch fonts.
Custom utilities
label-mono— the signature Ryme micro-label: JetBrains Mono,0.6875rem,0.16emletter-spacing, uppercase, muted color. Used everywhere from section labels to field captions.marquee— 45s linear infinite horizontal marquee (translateX0 → -50%); pauses on hover. Disabled automatically underprefers-reduced-motion.
Breakpoints & layout math
The layout is built around a 72rem content measure (max-w-6xl = 72rem) with elements that
anchor to its edges:
| Element | Visible at | Position |
| --- | --- | --- |
| GridStrip (left/right) | ≥ 80rem | calc(50% - 36rem - 28px) wide, flush to viewport edge |
| SectionBorder diamonds + line | ≥ 80rem | diamonds at calc(50% ± 36rem) and ± 36rem + 28px; line spans 72rem + 56px |
| SideLines (4 fixed lines) | always; anchored at ≥ 64rem | outer pair at calc(50% ± 36rem), inner pair 28px inside |
The 28px offset is the distance from the content edge to the outer diamond — all visuals
stay optically aligned with max-w-6xl content. SideLines are position: fixed so they
never cause horizontal scroll on viewports between 64–80rem; GridStrip and section borders
are hidden below 80rem to avoid overflow.
Overriding the theme
Override tokens on :root in your own CSS after importing ryme-ui/styles.css:
:root {
--primary: oklch(0.55 0.18 150); /* rebrand to green */
--ryme-grid-line: oklch(0.3 0.05 150);
}All components and visuals pick the new values automatically because they read the custom properties — no recompilation needed.
Components
Visuals
Visual components are purely decorative (aria-hidden="true"), pointer-events-none, and
render zero layout by themselves — position them yourself with wrappers.
Grainient
A full-screen WebGL 2 animated gradient: noise-driven rotation, sine warping, three-color blend, film grain, then contrast/gamma/saturation grading. Implemented with ogl (WebGL 2, GLSL ES 3.00) on a single triangle.
<div className="absolute inset-0">
<Grainient color1="#EF4444" color2="#B91C1C" color3="#000000" />
</div>It fills its parent (width: 100%; height: 100%) — give the parent a size.
All props are optional with these defaults:
| Prop | Default | Description |
| --- | --- | --- |
| color1 | #FF9FFC | Top/light blend color |
| color2 | #5227FF | Mid color |
| color3 | #B497CF | Bottom/dark color |
| timeSpeed | 0.25 | Animation speed multiplier |
| colorBalance | 0.0 | Shifts the blend edges |
| warpStrength | 1.0 | Sine-warp amplitude divisor |
| warpFrequency | 5.0 | Warp wave frequency |
| warpSpeed | 2.0 | Warp animation speed |
| warpAmplitude | 50.0 | Warp strength numerator |
| blendAngle | 0.0 | Rotation of the color blend axis (deg) |
| blendSoftness | 0.05 | Blend edge softness |
| rotationAmount | 500.0 | Noise-driven rotation range (deg) |
| noiseScale | 2.0 | Noise frequency |
| grainAmount | 0.1 | Film grain intensity (0 = off) |
| grainScale | 2.0 | Grain cell size |
| grainAnimated | false | Drift the grain over time |
| contrast | 1.5 | Output contrast |
| gamma | 1.0 | Output gamma |
| saturation | 1.0 | Output saturation |
| centerX / centerY | 0.0 / 0.0 | Shader-space center offset |
| zoom | 0.9 | Zoom into the gradient |
| className | "" | Extra classes on the wrapper |
Performance notes (all built-in, no work needed):
- One WebGL context per mount, created once and reused across prop updates (uniform writes only — zero GPU rebuilds).
- Animation pauses when the element scrolls out of view (
IntersectionObserver) and when the tab is hidden (visibilitychange). dprcapped at2,antialias: false(gradient + grain don't need AA).- Cleanup on unmount: context, observers, listeners and canvas are all removed.
- Respects
prefers-reduced-motionvia the global CSS base (smooth-scroll/marquee only — WebGL animation itself is user-configurable).
GridStrip
Inset side grid panels that flank the content on wide screens.
<GridStrip side="left" edgeCut />
<GridStrip side="right" edgeCut />| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| side | "left" \| "right" | "left" | Which viewport edge |
| edgeCut | boolean | true | Add a background-colored 40px edge strip for the cut corner look |
| className | string | "" | Extra classes |
Hidden below 80rem; position: absolute — render inside a position: relative wrapper
(usually right under <body>/root so it spans the whole page).
RuleGrid
A dense 64px hairline grid overlay with a radial mask, for hero backdrops.
<div className="relative">
<RuleGrid bleed mask="center" />
...content...
</div>| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| mask | "center" \| "bottom" \| "none" | "center" | Radial fade (center fades to 75%, bottom anchors at the bottom edge) |
| bleed | boolean | false | Extend inset to -2rem so lines run behind overflow content |
| className | string | "" | Extra classes |
Opacity is fixed at 0.25; line color comes from --ryme-grid-line.
SectionBorder
A <section> wrapper that draws the Ryme section top border: four rotated diamonds
(7px, red, haloed in background + border color) at the content edges, plus an optional
hairline.
<SectionBorder line>
<div className="mx-auto max-w-6xl px-5 py-20">...</div>
</SectionBorder>| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| line | boolean | false | Draw the centered hairline between the diamond pairs |
| className | string | "" | Extra classes (e.g. relative positioning context) |
| children | ReactNode | — | Section content |
Diamonds appear at ≥ 80rem only. SectionBorder renders a <section> — put id /
scroll-mt on inner content if you need anchor targets.
SideLines
The four fixed vertical hairlines framing the page (outer pair at the content measure, inner pair 28px inside):
<SideLines />No props. Renders four position: fixed aria-hidden divs, z-index: 40,
pointer-events: none. Anchored to calc(50% ± 36rem) at ≥ 64rem. Place once at the
app root, above page content.
GridPattern
An SVG <pattern> rect with optional highlighted "squares" — used for subtle dotted/grid
backdrops.
<GridPattern width={40} height={40} x={-1} y={-1} strokeDasharray="0" />| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| width / height | number | 40 / 40 | Pattern cell size |
| x / y | number | -1 / -1 | Pattern offset |
| squares | [number, number][] | — | Cell coordinates to render as filled squares |
| strokeDasharray | string | "0" | Dash pattern for the grid path |
| className | string | — | Extra classes |
| ...props | — | — | Passed through to the <svg> |
Fill/stroke color come from --ryme-grid-pattern-color.
Layout
SiteHeader
The sticky Ryme header: brand mark + wordmark, desktop nav, mobile hamburger with dropdown menu, and a "Book a demo" action button.
<SiteHeader linkComponent={Link} />| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| linkComponent | ComponentType<SiteLinkProps> | plain <a> | Router link component (see below) |
SiteLinkProps = { to?: string; hash?: string; className?: string; children?: ReactNode; onClick?: () => void }.
Pass your router's Link so internal navigation uses client-side routing; the default
renders <a href={to + (hash ? "#" + hash : "")}>.
import { Link } from "@tanstack/react-router";
<SiteHeader linkComponent={Link} />The nav items are fixed: Workflow, Agents, Skills, Internals (hash links to /), plus
/demo. Sticky top, z-50, bg-background/85 + backdrop blur, h-14.
SiteFooter
<SiteFooter noBorder linkComponent={Link} />| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| noBorder | boolean | false | Omit the top hairline (for bordered-page layouts) |
| linkComponent | SiteLinkComponent | plain <a> | Same router-linking pattern as the header |
Shows the Ryme Labs mark + wordmark, the authorized-testing tagline, and GitHub / X / Demo
links (https://github.com/Ryme-Labs/, https://x.com/rymelabs, /demo).
SectionHead
The standard section intro: mono micro-label over a large tight-tracked title.
<SectionHead id="workflow" label="How it runs" title="Five stages. One operator." />| Prop | Type | Description |
| --- | --- | --- |
| id | string (optional) | Anchor id (scroll-mt-16 included) |
| label | string | The mono uppercase eyebrow |
| title | string | The headline |
RymeMark
The geometric block-R SVG logo (viewBox 0 0 256 256, currentColor).
<RymeMark className="h-3.5 w-3.5 text-primary" />| Prop | Type | Description |
| --- | --- | --- |
| className | string | Size + color utilities (h-*, text-*) |
Use it for header/footer marks, favicons, loading screens — anywhere the brand mark is needed.
Utilities
cn
clsx + tailwind-merge — the standard class-name combiner. Later classes win, so
component overrides work predictably:
cn("px-4 py-2 rounded-md", "px-6"); // → "py-2 rounded-md px-6"useIsMobile
Reactive matchMedia("(max-width: 767px)") hook. Returns false on the first render
(SSR-safe), then the real value once mounted:
const isMobile = useIsMobile();UI components
45+ shadcn-style components, all re-exported from the package root. They follow the
standard shadcn/Radix APIs (variants via class-variance-authority, className merges
through cn), pre-themed with Ryme tokens.
| Group | Components |
| --- | --- |
| Actions | Button (default / outline / secondary / ghost / destructive / link, sm / lg / icon, asChild), Toggle, ToggleGroup, DropdownMenu, ContextMenu, Menubar, NavigationMenu |
| Overlays | AlertDialog, Dialog, Sheet, Drawer (vaul), Popover, HoverCard, Tooltip, Command, Sonner (toasts) |
| Forms | Input, Textarea, Label, Checkbox, RadioGroup, Select, Switch, Slider, InputOTP, Form (react-hook-form), Calendar (react-day-picker) |
| Data display | Card, Badge, Table, Skeleton, Avatar, Separator, AspectRatio, Chart (Recharts), Progress |
| Layout/nav | Sidebar, Breadcrumb, Pagination, Tabs, Accordion, ScrollArea, Resizable, Carousel (embla) |
Example — the Ryme look with zero extra styling:
import { Button, Card, CardContent, Input, Label } from "ryme-ui";
<Card>
<CardContent className="space-y-4 p-6">
<div className="space-y-1.5">
<Label>Email</Label>
<Input type="email" placeholder="[email protected]" />
</div>
<Button className="w-full">Continue</Button>
</CardContent>
</Card>All components inherit the dark theme automatically — no dark class or provider needed
(color-scheme: dark is baked into the base layer).
Guides
Building a hero section
<section className="relative flex min-h-screen items-center overflow-hidden border-b border-border bg-background">
{/* 1. Grainient backdrop */}
<div aria-hidden="true" className="absolute inset-0">
<Grainient color1="#EF4444" color2="#B91C1C" color3="#000000"
timeSpeed={0.2} warpStrength={1.2} rotationAmount={480} grainAmount={0.08}
contrast={1.4} zoom={0.9} />
</div>
{/* 2. Legibility scrim */}
<div aria-hidden="true"
className="absolute inset-0 bg-gradient-to-b from-background/80 via-background/35 to-background" />
{/* 3. Rule grid with bleed + center mask */}
<div className="relative mx-auto max-w-6xl px-5 py-24 sm:px-8">
<div className="relative">
<RuleGrid bleed mask="center" />
<h1 className="relative text-5xl tracking-tight sm:text-7xl">
Recon to report. <span className="text-primary">Proof or nothing.</span>
</h1>
</div>
</div>
</section>Full marketing page skeleton
export function LandingPage() {
return (
<div className="flex min-h-screen flex-col">
<SiteHeader linkComponent={Link} />
<main id="main" className="flex-1">
<Hero /> {/* raw <section>, see above */}
<SectionBorder> {/* metrics strip */}
<div className="mx-auto grid max-w-6xl grid-cols-2 px-5 py-8 lg:grid-cols-4">…</div>
</SectionBorder>
<SectionBorder line> {/* feature blocks */}
<div className="mx-auto max-w-6xl px-5 py-20 sm:px-8">
<SectionHead id="workflow" label="How it runs" title="Your headline." />
…
</div>
</SectionBorder>
<section className="relative overflow-hidden border-t border-border">
<RuleGrid mask="bottom" /> {/* CTA with bottom-masked grid */}
…
</section>
</main>
<SiteFooter noBorder linkComponent={Link} />
</div>
);
}Customizing the Grainient
The shader's warp/rotation/grain are independent levers — keep warpStrength ~1–2,
rotationAmount 400–600 and grainAmount 0.05–0.15 for the Ryme look; push timeSpeed
down for a calmer backdrop, raise contrast for a punchier band.
<Grainient color1="#7C3AED" color2="#C026D3" color3="#0F0A1F" rotationAmount={300} />Light theme / brand rebrand
:root {
--background: oklch(0.97 0.005 60);
--foreground: oklch(0.2 0.02 60);
--primary: oklch(0.5 0.2 265); /* blue */
--border: oklch(0.85 0.01 60);
--ryme-grid-line: oklch(0.8 0.01 60);
}Accessibility & performance
- Focus — global
:focus-visiblering using--ring; inputs/buttons ship Radix focus handling. - Reduced motion —
prefers-reduced-motiondisables smooth scrolling and the marquee animation via the base layer. - Semantics — visuals are
aria-hidden; headers render real<nav aria-label>; forms use real labels. - Grainient — auto-pauses offscreen/hidden,
dpr ≤ 2, single shared context per mount. - CSS — Tailwind v4 scans only
src/**(@source "../src"), so your CSS output stays small and utility classes are generated on demand.
Browser support
Modern evergreen browsers. WebGL 2 is required for Grainient; the component is a no-op
fallback (empty container) where WebGL is unavailable. ES2022 output — transpile for older
targets if needed.
Development
bun install # or npm install
bun run typecheck # tsc --noEmit
bun run dev # vite build --watch (rebuild dist on change)
bun run build # vite build && tsc -p tsconfig.build.json && copy README into disttsconfig.json— strict ES2022 + DOM,noEmitfor typechecking.tsconfig.build.json— emits declarations todistwithrootDir: src.vite.config.ts— library build withpreserveModules(per-file output mirrorssrc/), React + all runtime deps externalized, CSS extracted todist/ryme-ui.css, sourcemaps off.- The only bundled runtime is
ogl(used byGrainient); everything else is external.
Publishing
bun run release # npm version patch && npm publish && git push --follow-tagsprepare and prepublishOnly both run the build, so dist is always fresh. The build
also copies README.md into dist/ so the npm page renders it.
Package metadata: name: ryme-ui, license: UNLICENSED, type: module, exports map with
"." (ESM + types) and "./styles.css".
Troubleshooting
| Symptom | Cause / fix |
| --- | --- |
| Grainient shows nothing | Parent has no height — give it absolute inset-0 inside a sized/positioned wrapper, or an explicit height |
| Styles missing (components render unstyled) | import "ryme-ui/styles.css" is missing, or imported after your own theme overrides — import it first |
| Tailwind classes from ryme-ui components aren't generated | ryme-ui's CSS is pre-built and self-contained; no Tailwind config in your app is required. If your own utilities don't include a class you pass to a component, Tailwind v4 scans your files automatically |
| Side lines don't align with content | The 72rem measure assumes max-w-6xl content. Override --ryme-* positions or use a different content width with matching calc() offsets |
| SSR hydration warning about Grainient | Grainient mounts WebGL client-side only; it renders an empty div on the server, so hydration is stable |
| WebGL context loss on tab switch | Contexts are created per mount and paused offscreen — if you hot-reload aggressively, force a page reload once |
| marquee not animating | Verify prefers-reduced-motion is off; the class disables itself there by design |
| npm install of git dep fails | prepare needs bun (or npm) to run vite build — ensure devDependencies resolve in your install context |
License
UNLICENSED — private SDK for Ryme Labs projects. See the Ryme-Labs organization for product repos.
