Setup Guide
One-time setup for using NOIR // NEXT components. Add these to your project, then copy any component from the theme.
The cn() utility combines clsx and tailwind-merge for conditional class names. Install both packages.
npm install clsx tailwind-mergeCreate lib/utils.ts with the cn() helper. Every component imports this for class name merging.
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs))
}Add the NOIR // NEXT design tokens to your globals.css. These define colors, fonts, and animation tokens used by every component in the theme.
@theme {
/* ── Colors ─────────────────────────────────────────── */
--color-noir-black: #060608;
--color-noir-base: #080809;
--color-noir-surface: #0f0f12;
--color-noir-surface-2: #161619;
--color-noir-surface-3: #1d1d22;
--color-noir-border: #2a2a33;
--color-noir-border-soft: #1c1c22;
--color-noir-text: #eeeef4;
--color-noir-text-muted: #6c6c84;
--color-noir-text-dim: #38384a;
--color-noir-violet: #7c3aed;
--color-noir-violet-light: #a78bfa;
--color-noir-violet-dark: #5b21b6;
--color-noir-cyan: #22d3ee;
--color-noir-cyan-light: #a5f3fc;
--color-noir-cyan-dark: #0891b2;
--color-noir-green: #22c55e;
--color-noir-green-dark: #16a34a;
--color-noir-amber: #f59e0b;
--color-noir-amber-dark: #d97706;
--color-noir-red: #f43f5e;
--color-noir-red-dark: #e11d48;
--color-noir-white: #ffffff;
/* ── Fonts ───────────────────────────────────────────── */
--font-body: var(--font-barlow);
--font-mono: var(--font-ibm-plex-mono);
/* ── Animations ──────────────────────────────────────── */
--animate-blink: blink-cursor 1s step-end infinite;
--animate-pulse-glow: pulse-glow 3s ease-in-out infinite;
--animate-float: gentle-float 5s ease-in-out infinite;
--animate-scan: scan-line 4s linear infinite;
}Each component may need its own CSS classes in your globals.css. Copy the styles for the components you use.
/* ─── Buttons ────────────────────────────────────────── */
.btn-base {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: var(--font-body);
font-weight: 600;
border: 1px solid transparent;
border-radius: 8px;
cursor: pointer;
text-decoration: none;
line-height: 1;
letter-spacing: 0.02em;
transition: all 0.15s ease;
white-space: nowrap;
}
.btn-base:hover { text-decoration: none; transform: translateY(-1px); }
.btn-base:active { transform: translateY(0) !important; }
.btn-sm { font-size: 0.78rem; padding: 7px 14px; }
.btn-md { font-size: 0.875rem; padding: 10px 20px; }
.btn-lg { font-size: 0.95rem; padding: 13px 28px; }
.btn-primary {
background: var(--color-noir-violet);
color: var(--color-noir-white);
border-color: var(--color-noir-violet-dark);
box-shadow: 0 0 0 0 rgba(124, 58, 237, 0);
}
.btn-primary:hover {
background: var(--color-noir-violet-light);
border-color: var(--color-noir-violet);
box-shadow: 0 0 20px rgba(124, 58, 237, 0.35), 0 4px 16px rgba(0, 0, 0, 0.4);
color: var(--color-noir-white);
}
.btn-secondary {
background: transparent;
color: var(--color-noir-cyan);
border-color: rgba(34, 211, 238, 0.3);
}
.btn-secondary:hover {
background: rgba(34, 211, 238, 0.07);
border-color: rgba(34, 211, 238, 0.6);
box-shadow: 0 0 16px rgba(34, 211, 238, 0.15);
color: var(--color-noir-cyan);
}
.btn-ghost {
background: transparent;
color: var(--color-noir-text-muted);
border-color: var(--color-noir-border);
}
.btn-ghost:hover {
background: var(--color-noir-surface-2);
border-color: var(--color-noir-border);
color: var(--color-noir-text);
}/* ─── Badges ─────────────────────────────────────────── */
.badge {
display: inline-flex;
align-items: center;
gap: 5px;
font-family: var(--font-mono);
font-weight: 500;
font-size: 0.62rem;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 3px 9px;
border-radius: 4px;
border: 1px solid transparent;
}
.badge--violet { background: rgba(124, 58, 237, 0.12); color: var(--color-noir-violet-light); border-color: rgba(124, 58, 237, 0.25); }
.badge--cyan { background: rgba(34, 211, 238, 0.08); color: var(--color-noir-cyan); border-color: rgba(34, 211, 238, 0.2); }
.badge--green { background: rgba(34, 197, 94, 0.08); color: var(--color-noir-green); border-color: rgba(34, 197, 94, 0.2); }
.badge--amber { background: rgba(245, 158, 11, 0.10); color: var(--color-noir-amber); border-color: rgba(245, 158, 11, 0.22); }
.badge--red { background: rgba(244, 63, 94, 0.08); color: var(--color-noir-red); border-color: rgba(244, 63, 94, 0.2); }/* ─── Features Section ───────────────────────────────── */
.noir-card {
background: var(--color-noir-surface-2);
border: 1px solid var(--color-noir-border);
border-radius: 10px;
padding: 24px;
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease;
position: relative;
overflow: hidden;
}
.noir-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(124, 58, 237, 0.4), transparent);
opacity: 0;
transition: opacity 0.2s ease;
}
.noir-card:hover {
border-color: rgba(124, 58, 237, 0.3);
box-shadow: 0 0 0 1px rgba(124, 58, 237, 0.08), 0 8px 32px rgba(0, 0, 0, 0.4);
transform: translateY(-2px);
}
.noir-card:hover::before { opacity: 1; }
.card-title {
font-weight: 600;
font-size: 0.975rem;
color: var(--color-noir-text);
margin-bottom: 8px;
line-height: 1.3;
letter-spacing: -0.01em;
}
.card-description {
font-size: 0.855rem;
font-weight: 400;
color: var(--color-noir-text-muted);
line-height: 1.65;
}/* ─── Text Variants ──────────────────────────────────── */
.text-body { font-size: 0.95rem; line-height: 1.75; font-weight: 400; color: var(--color-noir-text-muted); }
.text-caption { font-size: 0.8rem; font-weight: 400; color: var(--color-noir-text-muted); }
.text-label { font-family: var(--font-mono); font-size: 0.65rem; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; }
.text-code { font-family: var(--font-mono); font-size: 0.85rem; color: var(--color-noir-cyan-light); }Button
Dark-mode button with primary, secondary, and ghost variants in three sizes. Renders as a Next.js Link when an href is provided.
Badge
Inline badge with violet, cyan, green, amber, and red variants for status labels and category tags in the NOIR dark palette.
Card
Composable dark card container with header, body, and footer sub-components in the NOIR aesthetic.
Text
Polymorphic text primitive with body, caption, label, and code variants styled for the NOIR dark palette.