@hex-core/components

164 Radix + CVA components incl. layout primitives + ColorPicker, Tailwind v4 entry.

Install

Version 1.12.0Runtime dependency
pnpm
pnpm add @hex-core/components

What it does

`@hex-core/components` is the runtime React component library. It ships 164 Radix + CVA components, including six layout primitives and an HSL ColorPicker.

The package owns the visual contract. `@hex-core/tokens` decides what colors mean; `@hex-core/components` decides how a Button looks when it's `variant="outline" size="sm"`. Consumers wire the two together via Tailwind v4's `@theme` block in their globals.css.

In Tailwind v4, utility classes inside published bundles are not auto-scanned. A `tailwind.css` entry (since 1.2.1) ships that adds the right `@source` directive — one `@import "@hex-core/components/tailwind.css"` and you're done.

Public API

AI / chat surface (20 components)

ts
import {
	Composer, Message, MessageList, MessageActions,
	Reasoning, Citation, ToolCall, Suggestion,
	CodeBlock, Markdown,
	Attachment, LoadingIndicator,
	Sources, InlineCitation, Task, Shimmer,
	Branch, Plan, Conversation, ChainOfThought,
	type Role, type ToolCallState,
} from "@hex-core/components";

The AI/chat cluster shipped across 1.6.0 → 1.7.0. `Composer` owns the input form (controlled value + onSubmit + a trailing-children slot for attachments / send buttons); `Message` + `MessageList` + `MessageActions` render conversation turns; `Reasoning`, `Citation`, `ToolCall`, `Suggestion` are the agent-affordance set; `CodeBlock` is an async Server Component (Shiki-powered), `Markdown` streams streamdown output, `Attachment` renders inline previews, and `LoadingIndicator` rounds out the multimodal surface. The 1.10.0 batch added eight more: `Sources` + `InlineCitation` for RAG provenance (footnote-style `[N](url)` links now route to the inline variant), `Task` for during/post-execution step progress (reusing the `ToolCallState` enum) vs `Plan` for the pre-execution approval gate, `Branch` for alternate-response navigation, `Conversation` as a high-level chat shell, `ChainOfThought` for ReAct-shape reasoning traces, and `Shimmer` as the streaming-text placeholder. All twenty ship with per-page references at `/docs/components/<slug>` (the 1.10.0 additions joined the registry in `@hex-core/payload@0.3.0`).

tsx
"use client";
import { useState } from "react";
import { Composer, Message, MessageList } from "@hex-core/components";

export function Chat({ messages, onSend }) {
	const [input, setInput] = useState("");
	const handleSubmit = (value: string) => {
		onSend(value);
		setInput("");
	};
	return (
		<div className="grid gap-3">
			<MessageList autoScroll>
				{messages.map((m) => (
					<Message key={m.id} role={m.role}>{m.content}</Message>
				))}
			</MessageList>
			<Composer
				value={input}
				onValueChange={setInput}
				onSubmit={handleSubmit}
				placeholder="Ask anything…"
			/>
		</div>
	);
}

New primitives (Empty / ErrorState / Loading / Tag / Toolbar / Tree)

ts
import {
	Empty, type EmptyProps, emptyVariants,
	ErrorState, type ErrorStateProps, errorStateVariants,
	Loading, type LoadingProps, loadingVariants,
	Tag, type TagProps, tagVariants,
	Toolbar, ToolbarButton, ToolbarSeparator,
	ToolbarToggleGroup, ToolbarToggleItem, type ToolbarProps,
	Tree, type TreeNode, type TreeProps,
} from "@hex-core/components";

Six primitives shipped in the 1.5.0 → 1.7.0 line. `Empty` / `ErrorState` / `Loading` are zero-state surfaces (each has `size: "sm" | "default" | "lg"`); `Tag` is the dismissable chip primitive distinct from `Badge` (carries an `onRemove` handler); `Toolbar` is the Radix toolbar with horizontal + vertical orientations and roving-tabindex arrow nav; `Tree` is the interactive WAI-ARIA tree (distinct from `FileTree`, which is the file-system-shaped variant). Each ships a per-page reference at `/docs/components/<slug>`.

tsx
import { Empty, Tag, Button } from "@hex-core/components";

export function NoFiltersYet() {
	return (
		<Empty
			title="No tags applied"
			description="Pick at least one tag to filter the catalog."
			action={<Button size="sm">Browse tags</Button>}
		/>
	);
}

export function FilterChips({ filters, onRemove }) {
	return filters.map((f) => (
		<Tag key={f.id} variant="secondary" onRemove={() => onRemove(f.id)}>
			{f.label}
		</Tag>
	));
}

Variant constants (17)

ts
import {
	buttonVariants, badgeVariants, toggleVariants,
	emptyVariants, loadingVariants, errorStateVariants, tagVariants,
	stackVariants, clusterVariants, gridVariants, spacerVariants,
	containerVariants, toolbarVariants,
	alertVariants, messageVariants, loadingIndicatorVariants, attachmentVariants,
} from "@hex-core/components";

Every CVA-built component re-exports its `*Variants` slot helper alongside the React component. Use them when you need to compose the same variant classes onto a different element — wrapping a `<Link>` to look like a button, applying `stackVariants` to a non-Stack flex container, etc. — without forking the component. Pair with `cn` from your project's `lib/utils` (the barrel `cn` is client-only — importing it into a Server Component will crash prerender).

tsx
import Link from "next/link";
import { buttonVariants } from "@hex-core/components";
import { cn } from "@/lib/utils";

export function CtaLink() {
	return (
		<Link
			href="/get-started"
			className={cn(buttonVariants({ variant: "outline", size: "lg" }), "w-full")}
		>
			Get started
		</Link>
	);
}

Button

ts
import { Button, type ButtonProps } from "@hex-core/components";

interface ButtonProps {
	variant?: "default" | "outline" | "secondary" | "ghost" | "link" | "destructive";
	size?: "default" | "sm" | "lg" | "icon";
	asChild?: boolean;
}

Polymorphic button with six variants and four sizes. `asChild` swaps the rendered element for the child component while preserving the variant classes — useful for wrapping `<Link>` or anchor tags without losing the visual contract.

tsx
import { Button } from "@hex-core/components";

export function ConfirmRow() {
	return (
		<div className="flex gap-2">
			<Button variant="outline">Cancel</Button>
			<Button>Confirm</Button>
		</div>
	);
}

Stack / Cluster / Grid / Container / Spacer / AspectRatio

ts
import {
	Stack, Cluster, Grid, Container, Spacer, AspectRatio,
} from "@hex-core/components";

interface StackProps {
	gap?: "xs" | "sm" | "md" | "lg" | "xl";
	align?: "start" | "center" | "end" | "stretch";
	justify?: "start" | "center" | "end" | "between";
}

Layout primitives shipped in 1.2.1. Stack = vertical flex; Cluster = horizontal flex with wrap; Grid = 1/2/3/4/6 columns or `auto-fit` with `minColWidth`; Container = max-width + padding; Spacer = declarative whitespace; AspectRatio = Radix re-export. They all share the same `gap` scale (drawn from `--space-*` tokens), so mixing them never produces uneven seams.

tsx
<Stack gap="md">
	<h2>Account</h2>
	<Cluster gap="sm">
		<Button variant="outline">Edit</Button>
		<Button variant="outline">Reset</Button>
	</Cluster>
</Stack>

ColorPicker

ts
import { ColorPicker, type ColorPickerProps } from "@hex-core/components";

interface ColorPickerProps {
	value: string; // "240 5.9% 10%" — same triplet @hex-core/tokens emits
	onChange: (value: string) => void;
	disabled?: boolean;
	"aria-label"?: string;
	className?: string;
}

HSL color picker. Round-trips losslessly through the same `<H> <S>% <L>%` triplet `@hex-core/tokens` exports, so binding it to a token override doesn't introduce float drift. `onChange` only fires when the input parses cleanly — invalid hex keeps the prior value.

tsx
"use client";
import { ColorPicker } from "@hex-core/components";
import { useState } from "react";

export function PrimaryEditor() {
	const [hsl, setHsl] = useState("240 5.9% 10%");
	return <ColorPicker aria-label="Primary color" value={hsl} onChange={setHsl} />;
}

Color utilities

ts
import {
	parseHslTriplet, formatHslTriplet,
	hslToRgb, rgbToHsl,
	hslTripletToHex, hexToHslTriplet,
	type HslTriplet, type RgbColor,
} from "@hex-core/components";

Pure conversions between the four canonical color formats the design system speaks: HSL triplets, HSL objects, RGB objects, and hex. Use these instead of hand-rolled regex — they handle the percent-vs-decimal lightness format that bites every consumer.

tsx
import { hexToHslTriplet, hslTripletToHex } from "@hex-core/components";

const triplet = hexToHslTriplet("#FF5733"); // "11 100% 60%"
const hex = hslTripletToHex("240 5.9% 10%"); // "#18181B"

tailwind.css entry

ts
/* @hex-core/components/tailwind.css — exported entry, not a TS API */
@source "./dist/*.js";

Single-line replacement for the manual `@source "../../node_modules/@hex-core/components/dist/*.js"` Tailwind v4 directive. Required for utility classes inside the published bundle to compile in your app. Add the import to your globals.css after `@import "tailwindcss";`.

css
@import "tailwindcss";
@import "@hex-core/components/tailwind.css";

@theme {
	--color-background: hsl(0 0% 100%);
	--color-foreground: hsl(240 10% 3.9%);
	/* ... */
}

Workflows

Add the package to a fresh Next.js + Tailwind v4 app

Three commands and one CSS import. Works the same on Vite, Remix, or any Tailwind v4 host.

bash
pnpm add @hex-core/components @hex-core/tokens
# ensure tailwindcss + @tailwindcss/postcss are already installed

Compose a settings card with layout primitives

Showcases Stack + Cluster + Grid sharing the same gap scale. No hand-rolled flex utilities.

tsx
import {
	Stack, Cluster, Grid, Card, CardHeader, CardContent,
	Button, Input, Label, Switch,
} from "@hex-core/components";

export function NotificationSettings() {
	return (
		<Card>
			<CardHeader><h2>Notifications</h2></CardHeader>
			<CardContent>
				<Stack gap="md">
					<Grid cols={2} gap="sm">
						<Stack gap="xs">
							<Label>Email</Label>
							<Input type="email" />
						</Stack>
						<Stack gap="xs">
							<Label>Push</Label>
							<Switch />
						</Stack>
					</Grid>
					<Cluster gap="sm" justify="end">
						<Button variant="outline">Cancel</Button>
						<Button>Save</Button>
					</Cluster>
				</Stack>
			</CardContent>
		</Card>
	);
}

Compatibility

  • Required peers: React 18 or 19 + Tailwind CSS v4.
  • Optional peers (each surfaces a CLI heavy-peer prompt when a consuming `hex add` pulls a component that needs it): `@tanstack/react-table` (DataTable), `cmdk` (Command / Combobox), `date-fns` + `react-day-picker` (Calendar / DatePicker), `input-otp` (InputOTP), `react-hook-form` (Form helpers), `react-resizable-panels` (Resizable), `sonner` (Toaster), `vaul` (Drawer). Skip the install only if you skip the component.
  • Lists `@hex-core/registry` as an optional peer; ships `sideEffects: false` for tree-shake. Internal dependencies — Radix primitives, CVA, clsx, tailwind-merge, Shiki, streamdown — are real npm deps that pin themselves.
  • External consumers pinning `@hex-core/registry` themselves should match `^0.3.0` to stay in sync with the schemas re-exported via `@hex-core/components/schemas`.
  • All components ship as ESM only.
  • **1.8.0 line:** added 5 multimodal AI components (`AudioPlayer`, `AudioWaveform`, `Canvas`, `Diagram`, `Terminal`) plus 23 artifact diagram primitives (`Arc`, `Chord`, `Cloze`, `CompareTable`, `Deck`, `Dendrogram`, `Flashcard`, `Flowchart`, `Funnel`, `Gantt`, `ImageOcclusion`, `Matrix`, `MindMap`, `OrgChart`, `Pyramid`, `Quiz`, `Sankey`, `Sequence`, `SpacedRepetition`, `Sunburst`, `TimeAxis`, `TreeMap`, `Venn`). Per-component `/docs/components/<slug>` pages shipped alongside the `@hex-core/payload@0.2.4` registry payload.
  • **1.9.0 line:** added the auth-block cluster — `AuthForgotPassword`, `AuthResetPassword`, `AuthSignInSplit`, `AuthSignUpCard`, `AuthVerifyEmail`, `AuthVerifyOtp` — plus `SpeechRecognition`. Each ships as a self-contained block (form + validation + accessible labels) you can drop into a route and wire a submit handler against. Per-component `/docs/components/<slug>` pages shipped alongside the same `@hex-core/payload@0.2.4` sync.
  • **1.10.0 → 1.12.0 line:** added three block families. The `app/*` family (`AppShell`, `AppSidebarNav`, `AppStats`, `AppSettings`, `AppDataTable`, plus `AppFeed`, `AppGridList`, `AppStackedList`) is the SaaS-shell scaffold; `commerce/*` (`CommerceCart`, `CommerceCheckout`, `CommerceProductGrid`, `CommerceProductDetail`, `CommerceReviews`, plus the category/order/promo extras) is the storefront kit; `marketing/*` (`MarketingHero`, `MarketingFeatureGrid`, `MarketingPricing`, `MarketingTestimonial`, `MarketingHeader`, `MarketingFooter`, `MarketingLogoCloud`, `MarketingCta`, plus the bento/faq/newsletter/stats/team/content/contact extras) is the landing-page kit. The 18 blocks listed first in each family ship with `/docs/components/<slug>` pages via `@hex-core/payload@0.3.0`; the extras ship at npm and pick up docs in the next payload bump. The motion primitives that shipped alongside this batch (`FadeIn`, `SlideIn`, `BlurIn`, `Marquee`, `Typewriter`, etc.) live in a sibling package `@hex-core/motion` — not re-exported from `@hex-core/components` — and will get their own mini-guide once the catalog adds the entry.

See also

Verified against @hex-core/components@1.12.0 · @hex-core/tokens@1.3.6 · @hex-core/themes@0.2.2 · @hex-core/registry@0.5.1 · @hex-core/payload@0.3.0