
Research
/Security News
Laravel Lang Compromised with RCE Backdoor Across 700+ Versions
Laravel Lang packages were compromised with an RCE backdoor across hundreds of versions, exposing cloud, CI/CD, and developer secrets.
voidframe-ui
Advanced tools
Dark monochrome React UI framework. Terminal-brutalist. Data-dense. Zero border-radius. Monospace-first.
Dark monochrome React UI framework. Terminal-brutalist. Data-dense. Zero border-radius. Monospace-first.
Built for dashboards, dev tools, data interfaces, internal consoles, AI chat products, and anything that needs to feel like it was forged from the void.
500+ accessible components across primitives, layout, forms, navigation, data display, overlays, interactive surfaces, a full chat/AI tier, domain-specific specialty widgets, and ~60 bundled monoline icons. Every stateful component is controllable or uncontrolled; every compound surface exposes a Radix-style dot-notation API; every interactive surface ships WAI-ARIA semantics and keyboard nav under CI-enforced jest-axe + Playwright coverage. Runtime dependencies: React and React-DOM — everything else is a peer.
VoidFrame targets modern evergreen browsers. The CSS uses color-mix() for button variant coloring, which requires:
All other CSS features (custom properties, logical properties, grid, container queries) have broader support than this baseline.
npm install voidframe-ui
Peer dependencies. Required: react >= 18.0.0, react-dom >= 18.0.0.
Optional (only required if you render the component on the right):
| Optional peer | Needed by |
|---|---|
d3-force | NetworkGraph |
d3-geo, topojson-client | ChoroplethMap, BubbleMap |
d3-hierarchy | TreeMap, Sunburst |
d3-sankey | Sankey |
d3-scale, d3-shape, d3-array, d3-time | All other charts (pulled in via the voidframe-ui/charts subpath) |
dompurify | MarkdownRenderer, MarkdownEditor preview |
react-live | docs/ site and any <Playground> consumer |
@testing-library/react, @testing-library/jest-dom, @testing-library/user-event, jest-axe | Required only when using voidframe-ui/testing's helpers |
Voidframe throws MissingPeerDependencyError at render time with the exact npm install command if an optional peer isn't resolvable. See the Charts section for per-chart install examples.
Import the stylesheet once at the top of your app:
import "voidframe-ui/styles.css";
The main entry re-exports the whole library. The subpaths below let you import only what you need or reach for helpers that aren't surfaced on the root entry:
| Subpath | When to use it |
|---|---|
voidframe-ui | Default — every component, hook, and utility. |
voidframe-ui/charts | Charts only (dead-code elimination if you don't touch any non-chart component). |
voidframe-ui/dev | <Playground>, <PropsTable>, <DevPanel>, ProfilerScope — dev-only surface, never ships to production. |
voidframe-ui/tokens | Token object (darkTheme, lightTheme, …) without pulling in React. Useful for tooling and design-tool sync. |
voidframe-ui/testing | renderWithTheme, expectNoA11yViolations, installMatchMedia, createMockStorage. |
voidframe-ui/primitives | Low-level primitives: Slot, Portal, FocusScope, DismissableLayer, Presence, LiveRegion, a11y helpers. |
voidframe-ui/core | Button, Badge, Card, Text, Loading, SplitButton, CopyButton, Result. |
voidframe-ui/layout | Layout family, AppShell, Sidebar, Masonry, Resizable, ScrollArea, responsive Show/ResponsiveBox. |
voidframe-ui/navigation | Menu, MegaMenu, BreadcrumbMenu, CommandPalette, Navbar, Toolbar, Wizard, Anchor, FloatingActionButton. |
voidframe-ui/forms | Every form control — inputs, date/time/color pickers, editors, FormProvider, Field, Combobox, Cascader, etc. |
voidframe-ui/data | DataGrid, TreeTable, TreeView, Virtualization, Viewers/*, Metrics, AsyncData, skeletons, descriptions. |
voidframe-ui/activity | Calendar, Gantt, Kanban, Comment, LiveIndicator, Activity. |
voidframe-ui/overlays | Dialog, Drawer, Popovers, Spotlight, ToastSystem, Notifications, Network, Popconfirm. |
voidframe-ui/media | Carousel, Lightbox, Image, MediaPlayer, Embed/RichEmbed, ImageDiff. |
voidframe-ui/animation | FadeIn, SlideIn, and the rest of the Animations module. |
voidframe-ui/icons | Icon, IconButton, the bundled icon set, and adaptIcon. |
voidframe-ui/chat | Chat surface — Conversation, Message*, ChatComposer, ChatSession, ModelCompare, TokenVisualizer. |
voidframe-ui/specialty | Niche surface: ThemeSelector, ShortcutGuide, Print, ColorTools, Numeric, TimeDisplays, etc. |
voidframe-ui/interactive | Accordion, DragDrop, Gestures, FilterBuilder, Interactive primitives. |
voidframe-ui/compat-shadcn | Migrating from shadcn/Radix — flat named exports with shadcn-shaped API. |
voidframe-ui/reactflow | VoidframeReactFlowTheme wrapper + useVoidframeReactFlowStyles() for @xyflow/react. |
voidframe-ui/styles.css | The single bundled stylesheet. |
voidframe-ui/theme-script.js | Inline <head> snippet that applies the persisted theme pre-hydration (no flash). |
For the vast majority of consumers — anyone using a bundler (Vite, Webpack, Rspack, Rollup, esbuild, Next.js, Remix, Astro, Parcel) — import { Button } from "voidframe-ui" is fine. The bundler tree-shakes unused modules, so pulling Button from the root bundle only ships Button.
The category subpaths (voidframe-ui/core, voidframe-ui/forms, voidframe-ui/data, …) exist for two cases:
Bundler-free consumers — raw Node scripts, Deno, Bun without a bundler, node --input-type=module, esm.sh, unpkg with ?module, etc. The monolithic root entry statically references chunks for every optional peer (dompurify, d3-*, react-live). Without tree-shaking, Node's ESM loader will try to resolve every referenced chunk at module-load time and fail if the optional peers aren't installed.
// ✗ needs dompurify + d3-* + react-live installed to even load
import { Button } from "voidframe-ui";
// ✓ loads clean with only react + react-dom
import { Button } from "voidframe-ui/core";
If you're writing a small script, importing from the relevant subpath is the right call.
Explicit boundary control — if you want to guarantee at review time that a file only reaches into one category, import { DataGrid } from "voidframe-ui/data" makes the intent reviewable. Some teams lint for this with the bundled voidframe-ui/prefer-subpath-import ESLint rule.
The voidframe-ui CLI can set up a fresh Vite + React project pre-wired
with the provider, stylesheet, and a starter page:
npx voidframe init my-app
cd my-app && npm install && npm run dev
Other CLI commands:
voidframe theme <dark|light|midnight|grey> — drop a theme override
file you can edit locally.voidframe codemod <name> <paths...> — run a voidframe codemod
(legacy-charts-to-v2, tokens-from-hex).voidframe doctor — verify your project wiring (voidframe version,
React version, stylesheet import, peer deps).voidframe test <name> [--type component|hook|util] [--force] —
scaffold a vitest file for a component, hook, or utility, with
prop-aware test stubs based on the source declaration.The Voidframe for VS Code extension ships snippets for every public component (trigger with vf-<name> or the PascalCase name), hover docs with prop tables, and an "Open Playground" command.
Install from the Marketplace, or build locally from tools/vscode-voidframe/:
code --install-extension tools/vscode-voidframe/vscode-voidframe-1.0.0.vsix
import {
VoidframeProvider,
AppShell,
Sidebar,
PageHeader,
Card,
Button,
Stat,
StatGroup,
Toaster,
toast,
} from "voidframe-ui";
function App() {
return (
<VoidframeProvider>
<AppShell
sidebar={
<Sidebar title="VOIDFRAME">
<Sidebar.Item href="#/">Dashboard</Sidebar.Item>
<Sidebar.Item href="#/ops">Operations</Sidebar.Item>
</Sidebar>
}
header={<PageHeader title="DASHBOARD" />}
>
<Card title="OVERVIEW">
<StatGroup>
<Stat label="USERS" value="12,847" color="#4ade80" />
<Stat label="REVENUE" value="$1.2M" color="#4ade80" />
</StatGroup>
<Button
variant="accent"
accent="#4ade80"
onClick={() => toast.success("Refreshed")}
>
REFRESH
</Button>
</Card>
</AppShell>
<Toaster />
</VoidframeProvider>
);
}
If you're moving an existing shadcn/ui (or Radix Primitives) project to voidframe-ui, the voidframe-ui/compat-shadcn subpath provides shadcn-shaped flat exports backed by voidframe internals. Most call sites can keep their existing imports verbatim; only the import path changes.
- import { Button } from "@/components/ui/button";
- import {
- Dialog,
- DialogTrigger,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogFooter,
- } from "@/components/ui/dialog";
+ import {
+ Button,
+ Dialog,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogFooter,
+ } from "voidframe-ui/compat-shadcn";
The compat layer is purely static re-exports — zero runtime translation cost. Behavior, a11y semantics, focus management, and DOM shape come from the underlying voidframe components.
| shadcn import | voidframe-ui/compat-shadcn export | voidframe equivalent |
|---|---|---|
Button, buttonVariants | Button | Button (variant default→solid, secondary→subtle, link→Link primitive) |
Card (+ Header/Title/Description/Content/Footer) | Card + same | Card (with composed slot wrappers) |
Dialog (+ all subcomponents) | Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, DialogClose | Dialog.* |
AlertDialog (+ subcomponents) | AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogAction, AlertDialogCancel | AlertDialog + Dialog.* slots |
Sheet (+ subcomponents) | Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter, SheetClose | DrawerV2.* (description aliases body) |
Popover (+ subcomponents) | Popover, PopoverTrigger, PopoverContent | PopoverV2.* |
Tooltip (+ TooltipProvider) | Tooltip, TooltipTrigger, TooltipContent, TooltipProvider | TooltipRoot.* (provider is a no-op pass-through for source-compat) |
DropdownMenu (+ Item / CheckboxItem / RadioGroup / RadioItem / Separator / Label / Sub*) | Same names with DropdownMenu prefix | Menu.* |
Select (+ Trigger / Value / Content / Item) | Same | Select.Root / .Trigger / .Value / .Content / .Item |
Tabs (+ List / Trigger / Content) | Same | Tabs.List / .Trigger / .Panel (aliased as .Content) |
Toast / Toaster / useToast | Same | Toaster + toast() (sonner-shaped toast.error, .success, .warning, .info also work) |
Input, Textarea, Label, Badge, Checkbox, Switch, Avatar, Separator, ScrollArea, Skeleton, Progress, Slider, Toggle, ToggleGroup, RadioGroup, RadioGroupItem | Trivial passthroughs | direct voidframe exports |
Button's variant="link" renders the voidframe Link primitive (an <a>-based element with accent + focus ring). If you were using Button variant="link" for a programmatic action (no href), switch to variant="ghost" + tone="info" instead.toast.error("message") works directly. The 2-arg signature toast(message, options) is supported alongside the existing single-options form.<Tooltip.Provider> is required-by-design in Radix; voidframe doesn't need one. The compat-layer TooltipProvider is a no-op <>{children}</> so existing <TooltipProvider delayDuration={...}> wrappers stay valid (the delayDuration prop is silently ignored — voidframe Tooltip handles its own timing).cva) is not bundled. If your shadcn project used cva for variant CSS, you can keep importing it from your own deps (it's tiny) or migrate to voidframe's tone/variant/size props which auto-emit data-tone/data-variant/data-size plus BEM classes.The compat layer is intentionally thin (~5 KB gzipped) — it's a migration aid, not a parallel implementation. Once your migration is done, you can switch each consumer over to native voidframe imports at your own pace.
Wrap your app (or any subtree) in VoidframeProvider. All child components read tokens from context.
import { VoidframeProvider } from "voidframe-ui";
<VoidframeProvider>
<App />
</VoidframeProvider>
Four themes ship out of the box: darkTheme (default), lightTheme, midnightTheme (deep-black OLED-friendly), and greyTheme (neutral mid-grey for print and projection). Pass any of them to VoidframeProvider's theme prop or address them by name via themeName="dark" | "light" | "midnight" | "grey" | "system".
import { VoidframeProvider } from "voidframe-ui";
// By name — sets data-vf-theme and uses the stylesheet cascade (no FOUC).
<VoidframeProvider themeName="midnight">
<App />
</VoidframeProvider>
// Or pass a full token set via `theme` for runtime overrides.
import { lightTheme } from "voidframe-ui";
<VoidframeProvider theme={lightTheme}>
<App />
</VoidframeProvider>
"system" — follow OS preference<VoidframeProvider themeName="system">
<App />
</VoidframeProvider>
Subscribes to prefers-color-scheme and re-resolves automatically.
import { VoidframeProvider, createTheme } from "voidframe-ui";
const warmVoid = createTheme({
bg0: "#0a0806",
bg1: "#0e0c0a",
green: "#86efac",
fontFamily: "'IBM Plex Mono', monospace",
});
<VoidframeProvider theme={warmVoid}>
<App />
</VoidframeProvider>
Independent, composable props on the provider — all cascade via data-* attributes so component CSS reads them through custom properties.
<VoidframeProvider
themeName="system"
density="compact" // comfortable | compact | spacious
contrast="high" // normal | high
direction="rtl" // ltr | rtl
reducedMotion="auto" // auto | always | never
>
<App />
</VoidframeProvider>
<ThemeScope>Override any of the above for a subtree without remounting the rest.
import { ThemeScope, lightTheme } from "voidframe-ui";
<VoidframeProvider themeName="dark">
<Header />
<ThemeScope themeName="light">
<Preview /> {/* this subtree renders in light */}
</ThemeScope>
<Footer />
</VoidframeProvider>
import { useThemePersistence, VoidframeProvider, ThemeSelector } from "voidframe-ui";
function App() {
const { theme, setTheme } = useThemePersistence({
key: "myapp-theme",
defaultTheme: "system",
allowed: ["dark", "light", "midnight", "system"],
});
return (
<VoidframeProvider themeName={theme}>
<ThemeSelector
value={theme}
onChange={setTheme}
themes={[
{ id: "dark", label: "Dark" },
{ id: "light", label: "Light" },
{ id: "midnight", label: "Midnight" },
{ id: "system", label: "Auto" },
]}
/>
</VoidframeProvider>
);
}
Reads from localStorage, survives reloads, and syncs across tabs via the storage event.
import { useTokens } from "voidframe-ui";
function MyComponent() {
const t = useTokens();
return <div style={{ color: t.green, fontFamily: t.fontFamily }}>OK</div>;
}
| Category | Tokens | Purpose |
|---|---|---|
| Surfaces | bg0 → bg5 | 6 depth layers, no shadows |
| Borders | border0 → border4 | 5 tiers, subtle to prominent |
| Text | text0 → text5 | 6 levels, white to invisible |
| Accents | green red amber blue purple cyan rose | Semantic color only |
| Aliases | success danger warning info | Semantic shortcuts |
| Typography | fontFamily, fontXxs → font3xl | 8 sizes, monospace only |
| Spacing | sp1 (2px) → sp12 (48px) | 12-step scale |
| Misc | radius (always 0), transition | Framework constants |
Everything below ships from the top-level voidframe-ui import. Compound components expose their subparts as dot-properties (e.g. Sidebar.Item, Menu.Trigger, Dialog.Content).
V2 components are the recommended versions.
DrawerV2,PopoverV2,AlertV2,ConfirmDialogV2,SpinnerV2, andToastersupersede the legacyDrawer,Popover,Alert,ConfirmDialog,Spinner, andToast. The legacy versions emit deprecation warnings and will be removed in v2.0.
| Component | Purpose |
|---|---|
Text, Label, Quote, Code, Kbd | Typographic primitives |
Divider, Spacer | Whitespace / separation |
Box, Flex, HStack, VStack, Stack, Grid, Container | Layout primitives |
AspectRatio | Fixed-ratio frames |
AppShell | Sidebar + header + footer scaffolding |
Sidebar (compound) | Collapsible navigation shell with groups and items |
PageHeader | Title / subtitle / action area for routes |
Navbar, Toolbar (compound) | Top-level bars and inline action strips |
SplitView, ResizableGroup / ResizablePanel / ResizableHandle | Draggable split panes |
ResizableBox | Standalone freeform resize (axis="x" | "y" | "both") with edge + corner grips |
ScrollArea, ScrollRow, ScrollIndicator, ScrollSpy | Scroll containers + indicators |
StatusBar, SegmentBar, SegmentedProgress | Horizontal status strips |
Frame, BannerAlert, Callout, OfflineBanner, ConnectionStatus | Framed content + top-of-page callouts |
| Want | Use |
|---|---|
| Primary action | variant="solid" |
| Secondary action | variant="outline" (default) |
| Destructive action | tone="danger" (or variant="destructive") |
| Subtle/ghost action | variant="ghost" or variant="subtle" |
| Custom brand color | accent="#hex" on any variant |
variant="destructive" is a shadcn-compat shorthand for variant="solid" tone="danger".
| Component | Purpose |
|---|---|
Button, ButtonGroup | Primary button + segmented group |
IconButton | Icon-only button |
Link | Text-link primitive — voidframe accent + focus ring; pass as for react-router / next/link integration |
Badge, Tag, TagInput | Small labels / chip collections |
Shortcut, Kbd, ShortcutGuide | Keyboard hints + ? registry overlay |
StatusIndicator, Dots | Presence dots and multi-dot stacks |
Spinner, SpinnerV2, Shimmer, Skeleton, Dots, Progress, CircularProgress, Gauge, Sparkline | Loading / progress primitives |
LoadingOverlay, Backdrop | Full-surface loading / dim layers |
Link — text-link primitiveimport { Link } from "voidframe-ui";
import NextLink from "next/link";
// Default — wraps native <a>, picks up voidframe accent + focus ring.
<Link href="/docs">Read the docs</Link>
// External target — adds rel="noopener noreferrer" + target="_blank".
<Link href="https://example.com" external>Example</Link>
// Polymorphic — render as a router-aware Link without losing styling.
<Link as={NextLink} href="/dashboard">Dashboard</Link>
Use Link whenever you would have reached for <a className="…"> and
hand-rolled accent/focus styling — the primitive keeps focus rings,
hover tones, and visited-link semantics consistent across the app.
For programmatic actions (no destination URL), use Button with
variant="ghost" instead — links should always navigate.
Core: Input, Textarea, Select, Toggle, Switch, Checkbox, CheckboxGroup, RadioGroup, Slider, NumberInput, CurrencyInput, PhoneInput, PinInput, PasswordInput, MaskedInput, SearchInput, SegmentedControl, RatingInput, ColorPicker.
Complex selects: Combobox, MultiSelect, TreeSelect, MentionInput.
Date & time: DatePicker, DateRangePicker, DateTimePicker, TimePicker (via DatePicker).
Editors: RichTextEditor, MarkdownEditor, CodeEditor, MarkdownRenderer.
Capture: SignaturePad, ImageCropper, FileUpload, Clipboard.
Forms hook: useForm() for controlled-or-uncontrolled form state with validation.
Every form control needs an accessible name. Voidframe surfaces a runtime
warning at mount when Input / Textarea / Toggle / Select are
rendered without one — keep the warnings clean and the components are
instantly screen-reader-correct.
Three accepted ways to name a control:
// 1. Built-in `label` prop (recommended) — voidframe wires htmlFor + id.
<Input label="Email" value={email} onValueChange={setEmail} />
// 2. <Field> compound — when a control has helpText / errorMessage / a
// custom label slot, wrap in Field for one-stop association.
<Field label="Email" helpText="We'll never share your email." error={errors.email}>
<Input value={email} onValueChange={setEmail} />
</Field>
// 3. aria-label — for inline / matrix / data-dense UIs where a visible
// label would be redundant. Voidframe accepts `asAriaLabel` as an
// explicit escape hatch + suppresses the missing-name warning.
<Select asAriaLabel="Tier backend" options={…} value={…} onValueChange={…} />
When to use Field: any time you need a label and helper / error /
description text. Single-purpose cases (just a label, no error UI) read
cleaner with the built-in label prop. The compound Field exists to
bundle the auxiliary slots into one consistent layout.
Switch vs Checkbox — both controllable, both keyboard-navigable, near-identical APIs. Pick by intent:
| Use | When |
|---|---|
<Switch> | Persistent app/system state — "Dark mode", "Refinement enabled", "Notifications on". The switch is the setting. |
<Checkbox> | Selection from a list, agreement to terms, multi-select rows — the choice is one of many. |
Mixed lists (e.g. settings panes that have both kinds) are fine; the visual distinction is part of why the two components exist.
Tabs, TabBar, Stepper, Breadcrumb, BreadcrumbMenu, Pagination, CursorPagination, Menu (compound), MenuBar + MenuBarMenu, MegaMenu, NavGroup, NavItem.
Tables: Table, DataGrid (sort / filter / resize / reorder / group / virtualize / persist / export), TreeTable, DataList, DescriptionList, KeyValue, JSONViewer, DiffViewer, LogViewer, Gantt, Kanban, Heatmap.
Trees & lists: TreeView, VirtualList (via DataGrid virtualization), Sortable.
Metrics: Stat, StatGroup, MetricCard, CircularProgress.
Charts — hand-rolled SVG library. Math uses d3-scale / d3-shape / d3-array / d3-time for algorithms; every rendered element, CSS class, and interaction is ours and themed through --vf-* tokens.
Primitives (Phase 21):
| Primitive | Purpose |
|---|---|
ChartFrame | SVG wrapper, margin accounting, scale context, responsive sizing |
Axis | 1px brutalist axis with nice-ticks for linear / log / time / band / point scales |
Gridlines | X / Y / both, solid or dashed |
ChartLegend | Swatches + labels, interactive toggle, tone variants |
ChartTooltip | Portaled tooltip with viewport-flip + edge-clamp math |
Crosshair | SVG guide lines + point at (x, y) in plot-space coords |
Brush | Drag-select range on X or Y axis, controlled + uncontrolled |
Chart families (Phase 22):
| Chart | Variants |
|---|---|
BarChart | vertical / horizontal, grouped / stacked / 100%-stacked |
LineChart | single + multi-series, linear/step/monotone/catmull-rom curves, dashed, crosshair |
AreaChart | single / stacked / 100%-stacked |
ScatterPlot, BubbleChart | square/circle/diamond/cross shapes, sqrt size scale for bubbles |
ComposedChart | bar + line + area + scatter on shared axes |
PieChart, DonutChart | configurable inner radius, pad/corner angles |
RadarChart | multi-series polygon over shared axes |
Histogram | auto-bin via d3-array, tooltip per bin |
CalendarHeatmap | GitHub contribution-graph style, quantized color scale |
Sparkline | inline micro-chart, optional area + trend + points |
Heatmap | row × column matrix, quantized color scale |
Advanced layouts (Phase 23):
| Chart | Notes |
|---|---|
TreeMap | squarified / binary / slice / dice / slice-dice tilings via d3-hierarchy |
Sunburst | radial partition with rotated labels for wide sweeps |
FunnelChart | vertical or horizontal drop-off, percent-of-top label |
WaterfallChart | running-sum bars with increase / decrease / total tones and connectors |
BoxPlot | min / Q1 / median / Q3 / max + outliers beyond 1.5 × IQR |
ViolinPlot | Gaussian-kernel density (Silverman bandwidth), falls back to rect for small samples |
CandlestickChart, OHLCChart | candle bodies or OHLC ticks with up/down tones |
StreamGraph | stacked area with wiggle baseline + inside-out order |
HorizonChart | N colored bands, negatives folded |
Sankey | node relaxation via d3-sankey with justify/left/right/center alignment |
ChordDiagram | flow between N groups from a square matrix |
ParallelCoordinates | N per-dimension linear axes with polylines per row |
ScatterMatrix | N × N scatter grid (SPLOM) |
SmallMultiples<T> | generic faceting wrapper used by SPLOM and any chart repeated over categories |
Geo + Network (Phase 24):
| Chart | Peer deps | Notes |
|---|---|---|
DependencyGraph | none | layered DAG, longest-path layering, top-down or left-right with arrowed orthogonal edges |
TileGridMap | none | equal-area square tiles, ships with US_STATES_GRID preset (50 + DC) |
NetworkGraph | d3-force (optional) | force-directed simulation, drag to pin nodes, auto-cools after coolDownAfter ms of quiet |
ChoroplethMap | d3-geo, topojson-client (optional) | quantized fill per feature, supports geoMercator/geoEqualEarth/geoNaturalEarth1/geoAlbersUsa |
BubbleMap | d3-geo, topojson-client (optional) | sized circles at [lon, lat] over a TopoJSON base layer |
If a chart's optional peer is not installed when it renders, voidframe throws MissingPeerDependencyError with the exact npm install command. Install only what you use:
npm install d3-force # NetworkGraph
npm install d3-geo topojson-client # ChoroplethMap + BubbleMap
Math utilities: linearScale, logScale, sqrtScale, timeScale, bandScale, pointScale, quantizeScale, generateTicks, stackSeries, resolveCurve, bisectNearest, scanNearest, seriesPalette, formatChartNumber.
Calendars: Calendar (month/week/day views, range selection).
Avatars & media: Avatar, AvatarGroup, Image, ImageGallery, AudioPlayer, VideoPlayer, DocumentPreview, IFrame, CodeBlock.
Modal, Dialog (compound), DrawerV2, Sheet (smooth continuous drag on the handle, snaps on release), PopoverV2, HoverCard, TooltipV2, ContextMenu, Menu, CommandPalette, Spotlight, NotificationCenter, EmptyState, ErrorState, ReactionPicker.
Toasts: toast() imperative API, useToast() hook, Toaster (placement wrapper), Snackbar (bottom-center preset), AlertV2.
Accordion (compound), Carousel, Marquee, Lightbox, Swipeable, SwipeActions, Zoomable, ShareButton, Activity, MediaPlayer.
Purpose-built surface for Claude-/ChatGPT-/agent-style products. Everything below is in the top-level voidframe-ui import.
Conversation + messages: Conversation, MessageList, MessageGroup, Message, MessageContent (with MessagePart[] — text / code / tool_use / tool_result / thinking), StreamingText, ThinkingIndicator / TypingIndicator, ReasoningTrace, MessageActions (compound: Copy/Regenerate/Edit/Delete/Share/Feedback/Pin/Branch), MessageFeedback, ReactionBar / MessageReactions, MessageEdit.
Attachments: AttachmentList, Attachment, ImageAttachment, FileAttachment, CodeAttachment, AudioAttachment.
Tool calls & agents: ToolCall, ToolCallGroup, AgentStep, AgentTrace, PlanDisplay.
Citations & sources: Citation, CitationList, SourceCard, SourceGrid, RAGContext.
Composer: Composer (compound: Toolbar, Input, Footer, Submit, AttachButton, SlashButton, MicButton, TokenCounter), ComposerAttachment, ComposerMicButton, SubmitButton, StopButton, RegenerateButton, SuggestionChips / QuickReplies, PromptTemplateList, PromptTemplateEditor, SlashCommandPicker, Mention.
Session & history: SessionList, SessionListItem, ConversationHeader, ConversationEmptyState.
Model, context, cost: ModelSelector, SystemPromptEditor, ChatTokenCounter, ContextWindow, CostDisplay, LatencyIndicator, UnreadBadge.
Debug & trace: DebugPanel, TraceViewer.
Layout patterns: ChatLayout (sidebar + conversation + inspector), SimpleChat, AgentRunner.
Domain surfaces that round out the tier-1 offering. Everything below is in the top-level voidframe-ui import.
Dev tools: CommitGraph, NetworkInspector (JSON headers/body drill-down), ConsoleOutput (level filter "warn+"), DebugTree (JSON or YAML via format prop — toYaml helper exported), KeyValueEditor, QueryBuilder (AND/OR groups + rules), ShortcutEditor (records chords to mod+shift+k-style strings with conflict detection).
Identity: UserCard, TeamCard, OrganizationCard, Identicon (deterministic 5×5 mirrored pattern), PresenceList (grouped by status, maxVisible + overflow).
Numeric: NumberDisplay, CurrencyDisplay, PercentDisplay (all locale-aware with auto-tone), BigNumber (hero metric with unit + delta + optional sparkline slot).
Time: TimeZoneSelect (searchable, Intl-driven), RelativeTime (auto-updating, pure Intl.RelativeTimeFormat), DurationDisplay (hms/compact/long), Countdown.
Help & changelog: HelpTooltip (CSS-positioned ? icon), ContextHelp (side-panel pattern), Changelog (per-entry added/fixed/changed/removed/security/deprecated), WhatsNewPopover (once-per-version, persists via injected storage for tests/SSR).
Encoding: QRCode, Barcode — both render placeholder patterns by default and accept a pre-computed matrix / pattern so consumers can pipe in output from a peer dep (qrcode-generator, jsbarcode) without bundling it.
Color: ColorSwatch, Palette.
Rich embeds: LegalText, Mermaid (accepts a loader prop to lazy-resolve the peer dep at runtime).
Widgets: WidgetShell (loading / error / empty states, optional drag + resize handles), DashboardGrid (CSS-grid placement, optional drag-to-swap with live drop indicator, optional corner-resize handles via resizable), packLayout / usePackedLayout helpers.
Print: PrintLayout (@media print optimized), PrintButton (prints a ref'd subtree via a transient iframe, preserving styles).
Monoline, 1px-stroke, 24×24 brutalist icon system. Decorative by default; supplying label promotes to role="img". Directional icons auto-mirror under [dir="rtl"]. spin and pulse animations respect prefers-reduced-motion.
Primitive: Icon (size xs/sm/md/lg/xl/xxl or raw number, flipX/flipY/rotate/spin/pulse/directional).
Bundled set (~60): PlusIcon, MinusIcon, CheckIcon, XIcon, EditIcon, TrashIcon, CopyIcon, DownloadIcon, UploadIcon, RefreshIcon, SaveIcon, ShareIcon, SendIcon, BookmarkIcon, PinIcon, ChevronUp/Down/Left/RightIcon, ArrowUp/Down/Left/RightIcon, HomeIcon, ExternalLinkIcon, MoreHorizontal/VerticalIcon, MenuIcon, FileIcon, FileTextIcon, FolderIcon, FolderOpenIcon, BoldIcon, ItalicIcon, UnderlineIcon, CodeIcon, QuoteIcon, LinkIcon, InfoIcon, WarningIcon, ErrorIcon, SuccessIcon, QuestionIcon, SpinnerIcon, CircleIcon, SquareIcon, StarIcon, HeartIcon, SettingsIcon, UserIcon, UsersIcon, LockIcon, UnlockIcon, EyeIcon, EyeOffIcon, SearchIcon, FilterIcon, SortIcon, MailIcon, BellIcon, MessageIcon, PlayIcon, PauseIcon, StopIcon, ClockIcon, CalendarIcon, SunIcon, MoonIcon, ChartBarIcon, ChartLineIcon, DatabaseIcon, TerminalIcon, CloudIcon, CaretIcon, LoadingDotsIcon.
Composition: IconButton (square, required aria-label, optional tooltip, active for toggles), IconGroup (row with gap or interleaved separator).
Third-party adapter: adaptIcon(Component, { defaultLabel, directional }) wraps any Lucide/Phosphor/Heroicons/Tabler icon so framework sizing, color, spin, and RTL mirroring all apply.
import { SearchIcon, IconButton, adaptIcon } from "voidframe-ui";
import { Compass } from "lucide-react";
const CompassIcon = adaptIcon(Compass, { defaultLabel: "Compass" });
<IconButton aria-label="Search" tooltip="Search docs (⌘K)">
<SearchIcon />
</IconButton>
<CompassIcon size="xl" />
Lazy wrappers. The heaviest components ship pre-wrapped as React.lazy exports so you can defer their chunks until mount. Pair with <Suspense> at the consumer:
import { Suspense } from "react";
import {
LazyModal, LazyDialog, LazyDrawer, LazyLightbox,
LazyDataGrid, LazyTreeTable, LazyGantt, LazyKanban,
LazyCodeEditor, LazyMarkdownEditor, LazyRichTextEditor,
LazyDatePicker, LazyDateRangePicker, LazyCalendar,
LazySparkline, LazyHeatmap,
LazySignaturePad, LazyImageCropper, LazyVideoPlayer,
} from "voidframe-ui";
<Suspense fallback={<Spinner />}>
<LazyDataGrid columns={cols} data={rows} />
</Suspense>
Memoized leaves. High-traffic stateless components — Button, Badge, Dots, Label, Divider, Spacer, Spinner, Kbd, Icon — are wrapped in React.memo. Re-renders skip when props are referentially stable, which is the typical case inside tables, feeds, and icon-heavy lists.
Tree-shaking. package.json declares sideEffects: ["*.css"]. Every export is named; barrels re-export without side effects. Bundlers drop unused components automatically — import { SearchIcon } from "voidframe-ui" costs you just the icon and its primitive.
CSS perf hints. Overlay panels (modals, drawers, popovers, tooltips, toasts, command palette) carry contain: layout paint so they don't invalidate the surrounding page on open/close. For virtualized content, opt in to content-visibility: auto via data-content-visibility="auto" or the vf-cv-auto class — browsers skip rendering off-screen descendants entirely.
<div data-content-visibility="auto">
{longList.map((row) => <Row key={row.id} {...row} />)}
</div>
Bundle budgets (enforced in CI via npm run size — see package.json size-limit):
| Entry | Budget (gzipped) |
|---|---|
Core ESM (dist/voidframe.es.js) | ≤200 KB |
Charts ESM (dist/charts.es.js) | ≤40 KB |
Dev ESM (dist/dev.es.js) | ≤10 KB |
Stylesheet (dist/voidframe.css) | ≤50 KB |
| All JS (ES + CJS, every entry) | ≤460 KB |
Tree-shaking still applies — import { Button } from "voidframe-ui" costs roughly 4–5 KB gzipped, import { SearchIcon } about 2 KB. Those per-import figures are measured ad-hoc, not enforced in CI; open a PR before relying on them for a strict budget.
Per-subpath sizes. A measured bytes-per-subpath table is generated from the current dist/ output by node scripts/bundle-sizes.mjs (run after npm run build) and committed to docs/bundle-sizes.md. Refer to it when picking the smallest subpath for a given import.
Production DCE. All dev-only warn() and warnOnce() calls are guarded by process.env.NODE_ENV !== "production" — bundlers strip them from production builds entirely, so warning message strings never ship.
Voidframe ships a voidframe-ui/testing subpath with the same helpers used internally — so consuming apps can write tests against our components with the provider, a11y checks, and viewport mocks pre-wired.
Two render helpers are exposed:
renderWithTheme — wraps in VoidframeProvider only. Smallest possible setup.renderWithVoidframe — wraps in VoidframeProvider + ConfirmProvider (and optionally a nested ThemeScope). Use when the component under test calls useConfirm() or other voidframe imperative hooks. Re-exports screen / waitFor / fireEvent / within / cleanup / act from testing-library so consumers don't need to dual-import.import { describe, expect, it } from "vitest";
import {
renderWithVoidframe,
expectNoA11yViolations,
installMatchMedia,
createMockStorage,
screen,
} from "voidframe-ui/testing";
import { MyFeature } from "./MyFeature";
it("renders inside the provider + passes axe", async () => {
const { container } = renderWithVoidframe(<MyFeature />, {
themeName: "light",
density: "compact",
direction: "rtl",
});
expect(screen.getByRole("button")).toBeInTheDocument();
await expectNoA11yViolations(container);
});
it("reacts to a viewport resize", () => {
const ctl = installMatchMedia(320);
renderWithVoidframe(<MyFeature />);
ctl.setWidth(1100); // flips min-width matches + fires listeners
ctl.restore();
});
it("persists theme through storage", () => {
const storage = createMockStorage();
// Pass `storage` into useThemePersistence / WhatsNewPopover.
});
Scripts (the framework's own CI matrix, mirrored in package.json):
npm run test # full vitest suite (5,157 tests as of 2026-04-28)
npm run test:watch # interactive
npm run test:coverage # v8 coverage + enforced floor thresholds
npm run test:ssr # renderToString smoke test per phase
npm run test:a11y # axe suite across every component
Coverage floors (enforced by vitest thresholds): lines / statements / functions ≥75%, branches ≥70%. Per-layer targets in the Phase 19 plan (utilities / hooks / primitives ≥95%) are aspirational and can be raised as the suite matures.
Playwright + Chromatic. Unit + SSR + axe is the baseline. For full-flow interaction tests (overlays, drag-drop, form submission) add Playwright against the shipped demo:
npx playwright install
npx playwright test
Use @axe-core/playwright for per-route browser-level a11y checks. For visual regression, point Chromatic at the demo build or publish Storybook — the setup lives in Phases 22 / 25 of the plan.
Voidframe is SSR-safe and carries "use client" directives on every stateful module, so it works out of the box with Next.js (App + Pages Router), Remix, Astro, Vite SSR, and Gatsby. A renderToString smoke test exercises a representative sample of every complexity tier on every commit.
Next.js (App Router): wrap the root layout in a thin client wrapper — this keeps the rest of the layout server-rendered while carving out a single client boundary for VoidframeProvider.
// app/providers.tsx
"use client";
import { VoidframeProvider } from "voidframe-ui";
export function AppProviders({ children }) {
return <VoidframeProvider>{children}</VoidframeProvider>;
}
// app/layout.tsx
import { AppProviders } from "./providers";
import "voidframe-ui/styles.css";
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
{/* Pre-hydration theme sync — no flash. */}
<script src="/vf-theme.js" />
</head>
<body><AppProviders>{children}</AppProviders></body>
</html>
);
}
Remix / Vite SSR / Astro: identical pattern — import VoidframeProvider in a client-only entry, include voidframe-ui/styles.css in your root layout, add the theme-sync script inline to <head>.
Pre-hydration theme script. Prevents the dark→light flash when a user has a persisted or "system" theme preference. Inline the script in <head> before your app bundle — it's shipped at the package root as voidframe-ui/theme-script.js:
<!-- via <script src> — shipped at the package root -->
<script src="/node_modules/voidframe-ui/theme-script.js"></script>
<!-- or inline — identical behavior -->
<script>
(function () {
try {
var t = localStorage.getItem("voidframe-theme");
if (!t || t === "system") {
t = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
}
document.documentElement.setAttribute("data-vf-theme", t);
} catch (e) {}
})();
</script>
The snippet runs synchronously, reads the user's persisted pref (the same voidframe-theme key useThemePersistence uses), and sets data-vf-theme before React takes over.
<HydrationBoundary>. For components that genuinely can't SSR (canvas, measured layouts, time-of-day text), wrap them to render a fallback until after hydration — no mismatch, no broken diff.
import { HydrationBoundary } from "voidframe-ui";
<HydrationBoundary fallback={<Skeleton lines={3} />}>
<SignaturePad />
</HydrationBoundary>
React Server Components. Stateful components carry "use client" — use them freely in client files. Pure display primitives (Text, Label, Divider, Badge, Icon, Box, Flex, Grid, Container, Code, Kbd) remain usable from RSC because they don't hook into any stateful context beyond CSS.
Full i18n surface — every built-in string translates, every layout mirrors in RTL, every number/date formats by locale. Backed entirely by Intl.* APIs (no date-fns/moment dependency).
import {
VoidframeProvider,
MessagesProvider,
useMessages,
formatCurrency,
formatDate,
formatRelativeTime,
pluralize,
pseudolocalize,
ja, ar, enXA, // locale packs
} from "voidframe-ui";
// Provider — pass a LocalePack (messages + direction + firstDayOfWeek).
<VoidframeProvider locale={ja}>
<App />
</VoidframeProvider>
// Or layer partial overrides over a pack:
<VoidframeProvider
locale={ar}
messages={{ dialog: { confirm: "OK" } }}
>
<App />
</VoidframeProvider>
// RTL auto-derives from the pack — explicit `direction` wins if supplied.
<VoidframeProvider locale={ar}> {/* dir="rtl" */}
<VoidframeProvider locale={ar} direction="ltr"> {/* forced LTR */}
// Inside components
function MyComponent() {
const { t, locale, direction, firstDayOfWeek } = useMessages();
return (
<>
<Button>{t("dialog.confirm")}</Button>
<span>{t("pagination.pageOf", { current: 2, total: 10 })}</span>
<span>{formatCurrency(1234.56, "EUR", locale)}</span>
<span>{formatDate(new Date(), locale, { dateStyle: "long" })}</span>
<span>{formatRelativeTime(Date.now() - 60_000, locale)}</span>
<span>
{pluralize(count, locale, {
one: `1 file`,
other: `${count} files`,
})}
</span>
</>
);
}
Shipped locale packs (tree-shakable — import only what you need):
en, es, fr, de, ja, zhCN, ar (RTL), he (RTL), plus enXA — a pseudolocale wrapper for text-expansion QA.
Pseudolocalization — drop in enXA or pseudolocalize(enMessages) to stress-test layout with 40%-longer diacritic-heavy strings:
<VoidframeProvider locale={enXA}>
Nothing hardcoded — t() paths like pagination.previous, dialog.cancel, table.noData, overlay.close, a11y.menu etc. resolve from the merged catalog. Override any subset via the messages prop without re-translating everything.
Breakpoint tokens (sm=640, md=768, lg=1024, xl=1280, xxl=1536), a Responsive<T> prop shape, CSS-based <Show> / <Hide> (no SSR hydration flash), a JS ResponsiveBox primitive, and hooks for dynamic resolution.
import {
Show,
Hide,
ResponsiveBox,
useBreakpoint,
useDeviceType,
useResponsive,
useContainerQuery,
} from "voidframe-ui";
// CSS-based visibility — no flash, no JS required.
<Show above="md"><DesktopNav /></Show>
<Hide above="md"><MobileMenu /></Hide>
<Show between={["md", "xl"]}><TabletBand /></Show>
// Every layout primitive now accepts Responsive<T> natively.
<Flex direction={{ base: "column", md: "row" }} gap={{ base: 4, md: 8 }} />
<Grid columns={{ base: 1, md: 2, lg: 3 }} gap={{ base: 8, md: 16 }} />
<Container maxWidth={{ base: "100%", lg: "1200px" }} />
<Text size={{ base: "sm", md: "md", lg: "lg" }} />
<Text size="responsive-xl">Hero headline</Text> {/* preset ladder */}
// Or use ResponsiveBox as an escape hatch with the full prop surface.
<ResponsiveBox
display="grid"
columns={{ base: 1, sm: 2, md: 3, lg: 4, xxl: 6 }}
gap={{ base: 8, md: 12, lg: 16 }}
p={{ base: 8, md: 16 }}
/>
// Hooks for behavior that can't be expressed in CSS.
function ChatHeader() {
const bp = useBreakpoint(); // "base" | "sm" | "md" | "lg" | "xl" | "xxl"
const device = useDeviceType(); // "mobile" | "tablet" | "desktop"
const variant = useResponsive({ base: "compact", md: "comfortable" });
return ...;
}
// Container queries — element-size responsiveness.
function Card({ children }) {
const ref = useRef<HTMLDivElement>(null);
const state = useContainerQuery(ref, {
small: "(max-width: 300px)",
medium: "(min-width: 301px) and (max-width: 600px)",
large: "(min-width: 601px)",
});
return <div ref={ref} data-container="inline">...</div>;
}
Container query polyfill. The data-container="inline" / "size" CSS hooks apply container-type: inline-size / size on modern browsers (>93% support as of 2026). For older browsers, add the container query polyfill to your bundle:
import "container-query-polyfill";
The useContainerQuery hook above doesn't depend on browser CSS support — it measures via ResizeObserver and evaluates the predicates locally — so it always works.
Adaptive components. Modal, Drawer, DrawerV2, Sidebar, and Table accept an adaptive prop (default true) that changes behavior below the md breakpoint:
Modal goes full-screen.Drawer / DrawerV2 expand to full-width.Sidebar collapses to a 48px rail (labels hidden).Table renders as stacked cards (column header above each value).Opt-out with adaptive={false} when you're wrapping one of these inside your own mobile-aware shell.
VoidframeProvider — theme context.
ConfirmProvider + useConfirm() — promise-based confirmation dialogs.
ShortcutProvider + useShortcut() — global keyboard shortcut registry.
Theming & state:
useTokens, useControllableState, useHover, useFocus, useToggle, useClickOutside, useDebounce, useMediaQuery, useLocalStorage, useInterval, usePrevious, useForceUpdate, useMergedRefs, useId, useIsomorphicLayoutEffect.
Input & IO: useKeyboardShortcut, useCopyToClipboard, useScroll, useWindowSize, useShortcut, useShortcutRegistry.
Forms: useForm (see Forms section).
Toasts: useToast, plus module-level toast.success / .info / .warning / .danger / .promise / .dismiss.
cx, tint, formatNumber, formatBytes, formatDuration, timeAgo, truncate, clamp, mapRange, stringToColor, adjustColor, deepMerge, uid, groupBy, sortBy, copyToClipboard, createSafeContext, genericForwardRef, deprecatedProp, deprecatedComponent.
bg0→bg5 creates hierarchy without box-shadows.value/defaultValue pair so you can drop it in without a reducer.import {
VoidframeProvider,
ChatLayout,
SessionList,
Conversation,
ConversationHeader,
MessageList,
Message,
MessageContent,
Composer,
ToolCall,
toast,
} from "voidframe-ui";
function ChatApp() {
return (
<VoidframeProvider>
<ChatLayout
sidebar={<SessionList sessions={sessions} activeId={id} onSelect={load} />}
conversation={
<Conversation status={status}>
<ConversationHeader title={title} model={model} />
<MessageList>
{messages.map((m) => (
<Message key={m.id} role={m.role} author={m.author}>
<MessageContent content={m.content} streaming={m.streaming} />
{m.toolCalls?.map((tc) => <ToolCall key={tc.id} {...tc} />)}
</Message>
))}
</MessageList>
<Composer value={draft} onChange={setDraft} onSubmit={send} status={status}>
<Composer.Toolbar>
<Composer.AttachButton />
</Composer.Toolbar>
<Composer.Input placeholder="Message…" />
<Composer.Footer>
<Composer.TokenCounter />
<Composer.Submit />
</Composer.Footer>
</Composer>
</Conversation>
}
/>
</VoidframeProvider>
);
}
A live, section-by-section showcase covers every shipped component. Run it locally:
docker compose up demo # serves http://localhost:5173
Demo entry: demo/App.tsx. Sections are defined as plain components and registered in a SECTIONS array — add your own by appending one.
npm install
npm run build # outputs dist/voidframe.es.js, dist/voidframe.cjs.js, dist/voidframe.css
npm run test # full vitest suite (5,157 tests as of 2026-04-28)
npm run typecheck # tsc --noEmit
voidframe/
├── src/
│ ├── index.ts # Top-level barrel
│ ├── tokens.ts # Design tokens + createTheme + lightTheme
│ ├── provider/ # VoidframeProvider
│ ├── primitives/ # Slot, Portal, FocusScope, Presence, DismissableLayer
│ ├── hooks/ # All hooks
│ ├── utils/ # cx, formatters, polymorphic helpers
│ ├── components/ # Every component in the inventory above
│ └── css/ # Component stylesheets
├── demo/ # Section-by-section showcase app
├── package.json
├── vite.config.ts
└── README.md
The canonical repository lives at github.com/DaxAvalon/voidframe-ui. Issues, patches, and long-form discussion happen there. See CONTRIBUTING.md for workflow, coding standards, and notes for AI coding assistants working in this repo.
Local workflow:
git clone https://github.com/DaxAvalon/voidframe-ui.git
cd voidframe-ui
npm install
npm test # unit + a11y + SSR matrix
npm run docs # local docs site on :5175
npm run demo # live demo on :5173
Before opening a patch, run npm run typecheck && npm test && npm run size.
Phase plans and audit docs live under plans/; read the relevant one
before adding components to a tier.
MIT
FAQs
Dark monochrome React UI framework. Terminal-brutalist. Data-dense. Zero border-radius. Monospace-first.
We found that voidframe-ui demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Laravel Lang packages were compromised with an RCE backdoor across hundreds of versions, exposing cloud, CI/CD, and developer secrets.

Security News
Socket found a malicious postinstall hook across 700+ GitHub repos, including PHP packages on Packagist and Node.js project repositories.

Security News
Vibe coding at scale is reshaping how packages are created, contributed, and selected across the software supply chain