bjork-ui
componentprimitive-data-tablesource: ok · hit
Data Table
Data Table is a production-ready ui component from BJORK UI, packaged for shadcn registries.
npx shadcn add "https://ui.isaiahbjork.com/primitive-data-table.json"free
Where it goes
data-table
Use cases
dashboard
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · mediumanimationframer-motion
iconslucide-react
Files8 · source cached 10/8/2026, 10:24:53 PM
components/bjork-ui/primitives/data-table.tsx
"use client";
import { Search, SlidersHorizontal } from "lucide-react";
import { BjorkButton } from "./button";
import { BjorkInput } from "./input";
import { BjorkTable } from "./table";
export function BjorkDataTable() {
return (
<div className="w-[min(620px,100%)] space-y-3">
<div className="flex items-center justify-between gap-3">
<BjorkInput
icon={<Search />}
placeholder="Filter accounts"
className="h-10 max-w-[300px]"
/>
<BjorkButton variant="quiet" size="sm">
<SlidersHorizontal aria-hidden="true" />
View
</BjorkButton>
</div>
<BjorkTable />
</div>
);
}
components/bjork-ui/primitives/button.tsx
"use client";
import * as React from "react";
import { motion, useReducedMotion, type HTMLMotionProps } from "framer-motion";
import { cva, type VariantProps } from "class-variance-authority";
import { useWebHaptics } from "web-haptics/react";
import type { HapticInput, TriggerOptions } from "web-haptics";
import { cn } from "@/lib/utils";
const bjorkButtonVariants = cva(
"inline-flex shrink-0 transform-gpu items-center justify-center gap-2 whitespace-nowrap rounded-[13px] text-sm font-medium tracking-[-0.01em] outline-none backdrop-blur-md transition-[background-color,border-color,color,box-shadow,opacity] duration-150 ease-out focus-visible:ring-2 focus-visible:ring-[color:var(--bjork-accent)]/45 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bjork-ring-offset)] disabled:pointer-events-none disabled:opacity-35 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"border border-transparent bg-[var(--bjork-field)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-surface)] hover:bg-[var(--bjork-surface-hover)]",
secondary:
"border border-[color:var(--bjork-border)] bg-[var(--bjork-surface-active)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-soft)] hover:bg-[var(--bjork-surface-hover)]",
outline:
"border border-[color:var(--bjork-border)] bg-[var(--bjork-field-inset)] text-[color:var(--bjork-text-strong)] shadow-[var(--bjork-shadow-soft)] hover:border-[color:var(--bjork-border-strong)] hover:bg-[var(--bjork-surface)] hover:text-[color:var(--bjork-text)]",
ghost:
"border border-transparent bg-transparent text-[color:var(--bjork-text-muted)] hover:bg-[var(--bjork-surface-hover)] hover:text-[color:var(--bjork-text)]",
quiet:
"border border-transparent bg-[var(--bjork-field-muted)] text-[color:var(--bjork-text-soft)] hover:bg-[var(--bjork-surface-hover)] hover:text-[color:var(--bjork-text-medium)]",
accent:
"bjork-layered-button-accent tracking-tight text-white",
raised:
"bjork-layered-button-raised tracking-tight",
},
size: {
sm: "h-8 px-3 text-xs has-[>svg:first-child]:pl-2.5 has-[>svg:last-child]:pr-2.5",
md: "h-[45px] px-4 has-[>svg:first-child]:pl-3.5 has-[>svg:last-child]:pr-3.5",
lg: "h-12 px-5 text-[15px] has-[>svg:first-child]:pl-4.5 has-[>svg:last-child]:pr-4.5",
icon: "size-[45px] p-0",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
},
);
export interface BjorkButtonProps
extends Omit<HTMLMotionProps<"button">, "ref" | "size">,
VariantProps<typeof bjorkButtonVariants> {
haptics?: boolean;
hapticPattern?: HapticInput;
hapticOptions?: TriggerOptions;
}
export function BjorkButton({
className,
variant,
size,
disabled,
haptics = true,
hapticPattern = "light",
hapticOptions,
onPointerDown,
tabIndex,
...props
}: BjorkButtonProps) {
const shouldReduceMotion = useReducedMotion();
const { trigger } = useWebHaptics();
const handlePointerDown = React.useCallback(
(event: React.PointerEvent<HTMLButtonElement>) => {
if (!disabled && haptics) {
void trigger(hapticPattern, hapticOptions);
}
onPointerDown?.(event);
},
[disabled, hapticOptions, hapticPattern, haptics, onPointerDown, trigger],
);
return (
<motion.button
data-slot="bjork-button"
className={cn(bjorkButtonVariants({ variant, size, className }))}
disabled={disabled}
onPointerDown={handlePointerDown}
whileHover={disabled || shouldReduceMotion ? undefined : { scale: 1.05 }}
whileTap={disabled || shouldReduceMotion ? undefined : { scale: 0.95 }}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
tabIndex={tabIndex ?? 0}
{...props}
/>
);
}
export { bjorkButtonVariants };
lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
components/bjork-ui/primitives/input.tsx
"use client";
import * as React from "react";
import { motion, useReducedMotion, type HTMLMotionProps } from "framer-motion";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const bjorkInputVariants = cva(
"flex w-full min-w-0 rounded-[13px] border-0 bg-[var(--bjork-field)] text-[color:var(--bjork-text)] outline-none backdrop-blur-md transition-[background-color,border-color,box-shadow,opacity,transform] duration-200 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-[color:var(--bjork-text-soft)] selection:bg-[#ec5c13]/30 selection:text-[color:var(--bjork-text)] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-35 focus-visible:ring-2 focus-visible:ring-[color:var(--bjork-border-strong)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bjork-ring-offset)]",
{
variants: {
variant: {
default:
"shadow-[var(--bjork-shadow-surface)] hover:bg-[var(--bjork-surface-hover)]",
quiet:
"bg-[var(--bjork-field-muted)] text-[color:var(--bjork-text-strong)] shadow-[var(--bjork-shadow-soft)] hover:bg-[var(--bjork-surface-hover)]",
inset:
"border border-[color:var(--bjork-border)] bg-[var(--bjork-field-inset)] shadow-[var(--bjork-shadow-inset)] hover:bg-[var(--bjork-panel)]",
ghost:
"bg-transparent shadow-none hover:bg-transparent",
error:
"border border-[#ec5c13]/42 bg-[var(--bjork-error-bg)] shadow-[inset_0_7px_14px_rgba(236,92,19,0.035),0_8px_14px_-13px_rgba(236,92,19,0.28)] focus-visible:ring-[color:var(--bjork-border-strong)]",
},
inputSize: {
sm: "h-9 px-3 text-xs",
md: "h-[45px] px-4 text-sm",
lg: "h-12 px-4 text-[15px]",
},
},
defaultVariants: {
variant: "default",
inputSize: "md",
},
},
);
export interface BjorkInputProps
extends Omit<HTMLMotionProps<"input">, "ref" | "size">,
VariantProps<typeof bjorkInputVariants> {
icon?: React.ReactNode;
}
export function BjorkInput({
className,
icon,
variant,
inputSize,
type = "text",
...props
}: BjorkInputProps) {
const shouldReduceMotion = useReducedMotion();
const input = (
<motion.input
data-slot="bjork-input"
type={type}
className={cn(
bjorkInputVariants({
variant,
inputSize,
className: cn(icon && "pl-9", className),
})
)}
whileFocus={shouldReduceMotion ? undefined : { y: -1 }}
transition={{ type: "spring", stiffness: 360, damping: 30 }}
{...props}
/>
);
if (!icon) return input;
return (
<div className="relative w-full min-w-0">
<span className="pointer-events-none absolute left-3 top-1/2 z-10 -translate-y-1/2 text-[color:var(--bjork-text-soft)] [&>svg]:size-4">
{icon}
</span>
{input}
</div>
);
}
export { bjorkInputVariants };
components/bjork-ui/primitives/table.tsx
"use client";
import * as React from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { cn } from "@/lib/utils";
import { BjorkBadge } from "./badge";
import { bjorkCardVariants } from "./card";
const defaultRows = [
{ account: "Northwind", status: "Active", value: "$12,480" },
{ account: "Acme", status: "Queued", value: "$8,140" },
{ account: "Linear", status: "Review", value: "$6,920" },
];
export function BjorkTable({
rows = defaultRows,
}: {
rows?: typeof defaultRows;
}) {
return (
<div
data-slot="bjork-table"
className={cn(
"w-[min(620px,100%)] overflow-hidden",
bjorkCardVariants({ variant: "surface", padding: "none" }),
)}
>
<Table>
<TableHeader className="bg-[var(--bjork-field-muted)] shadow-[var(--bjork-shadow-soft)] dark:bg-[var(--bjork-field-inset)] dark:shadow-[var(--bjork-shadow-inset)]">
<TableRow className="border-[color:var(--bjork-border-muted)] hover:bg-transparent">
<TableHead className="h-10 px-4 text-xs font-medium uppercase tracking-[0.08em] text-[color:var(--bjork-text-soft)]">
Account
</TableHead>
<TableHead className="h-10 px-4 text-xs font-medium uppercase tracking-[0.08em] text-[color:var(--bjork-text-soft)]">
Status
</TableHead>
<TableHead className="h-10 px-4 text-right text-xs font-medium uppercase tracking-[0.08em] text-[color:var(--bjork-text-soft)]">
Value
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.map((row) => (
<TableRow
key={row.account}
className="border-[color:var(--bjork-border-muted)] transition-colors hover:bg-[var(--bjork-surface-hover)]"
>
<TableCell className="px-4 py-3 text-[color:var(--bjork-text-medium)]">{row.account}</TableCell>
<TableCell className="px-4 py-3">
<BjorkBadge variant={row.status === "Active" ? "accent" : "muted"}>
{row.status}
</BjorkBadge>
</TableCell>
<TableCell className="px-4 py-3 text-right font-mono text-[color:var(--bjork-text-muted)]">
{row.value}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}
components/ui/table.tsx
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
<div
data-slot="table-container"
className="relative w-full overflow-x-auto"
>
<table
data-slot="table"
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
)
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return (
<thead
data-slot="table-header"
className={cn("[&_tr]:border-b", className)}
{...props}
/>
)
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
return (
<tbody
data-slot="table-body"
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
)
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
return (
<tfoot
data-slot="table-footer"
className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
)
}
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
return (
<tr
data-slot="table-row"
className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
className
)}
{...props}
/>
)
}
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
return (
<th
data-slot="table-head"
className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
)
}
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
return (
<td
data-slot="table-cell"
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
)
}
function TableCaption({
className,
...props
}: React.ComponentProps<"caption">) {
return (
<caption
data-slot="table-caption"
className={cn("text-muted-foreground mt-4 text-sm", className)}
{...props}
/>
)
}
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
components/bjork-ui/primitives/badge.tsx
"use client";
import * as React from "react";
import { motion, useReducedMotion, type HTMLMotionProps } from "framer-motion";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const bjorkBadgeVariants = cva(
"inline-flex w-fit shrink-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-[9px] border font-medium outline-none transition-[background-color,border-color,color,box-shadow,opacity,transform] duration-200 focus-visible:ring-2 focus-visible:ring-[color:var(--bjork-accent-muted)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bjork-ring-offset)] [&_svg]:size-3",
{
variants: {
variant: {
default:
"border-transparent bg-[var(--bjork-text)] text-[color:var(--bjork-inverted-text)] shadow-[var(--bjork-shadow-soft)]",
secondary:
"border-transparent bg-[var(--bjork-surface-active)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-soft)]",
outline:
"border-[color:var(--bjork-border)] bg-[var(--bjork-field-inset)] text-[color:var(--bjork-text-medium)]",
muted:
"border-transparent bg-[var(--bjork-field-muted)] text-[color:var(--bjork-text-soft)]",
accent:
"border-[color:var(--bjork-accent-muted)] bg-[var(--bjork-accent-badge)] text-[color:var(--bjork-accent-badge-foreground)] shadow-[var(--bjork-shadow-soft)]",
},
size: {
sm: "px-1.5 py-0.5 text-[11px] has-[>svg:first-child]:pl-1 has-[>svg:last-child]:pr-1",
md: "px-2 py-1 text-xs has-[>svg:first-child]:pl-1.5 has-[>svg:last-child]:pr-1.5",
lg: "px-2.5 py-1.5 text-sm has-[>svg:first-child]:pl-2 has-[>svg:last-child]:pr-2",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
},
);
export interface BjorkBadgeProps
extends Omit<HTMLMotionProps<"span">, "ref">,
VariantProps<typeof bjorkBadgeVariants> {}
export function BjorkBadge({
className,
variant,
size,
...props
}: BjorkBadgeProps) {
const shouldReduceMotion = useReducedMotion();
return (
<motion.span
data-slot="bjork-badge"
className={cn(bjorkBadgeVariants({ variant, size, className }))}
whileHover={shouldReduceMotion ? undefined : { y: -1, opacity: 0.92 }}
transition={{ type: "spring", stiffness: 420, damping: 30 }}
{...props}
/>
);
}
export { bjorkBadgeVariants };
components/bjork-ui/primitives/card.tsx
"use client";
import * as React from "react";
import { motion, useReducedMotion, type HTMLMotionProps } from "framer-motion";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const bjorkCardVariants = cva(
"relative flex flex-col overflow-hidden rounded-[20px] border text-[color:var(--bjork-text)] outline-none transition-[background-color,border-color,box-shadow,opacity,transform] duration-300 focus-visible:ring-2 focus-visible:ring-[#ec5c13]/45 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bjork-ring-offset)]",
{
variants: {
variant: {
surface:
"border-[color:var(--bjork-border-muted)] bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-surface)]",
elevated:
"border-[color:var(--bjork-border-muted)] bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-surface)]",
quiet:
"border-[color:var(--bjork-border-muted)] bg-[var(--bjork-panel)] shadow-[var(--bjork-shadow-soft)]",
interactive:
"cursor-pointer border-[color:var(--bjork-border-muted)] bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-surface)] hover:border-[color:var(--bjork-border)] hover:bg-[var(--bjork-surface-hover)]",
},
padding: {
none: "p-0",
sm: "p-3",
md: "p-4",
lg: "p-5",
},
},
defaultVariants: {
variant: "surface",
padding: "md",
},
},
);
export interface BjorkCardProps
extends Omit<HTMLMotionProps<"div">, "ref">,
VariantProps<typeof bjorkCardVariants> {}
export function BjorkCard({
className,
variant,
padding,
...props
}: BjorkCardProps) {
const shouldReduceMotion = useReducedMotion();
const isInteractive = variant === "interactive" || props.role === "button";
return (
<motion.div
data-slot="bjork-card"
className={cn(bjorkCardVariants({ variant, padding, className }))}
whileHover={isInteractive && !shouldReduceMotion ? { y: -2, scale: 1.005 } : undefined}
whileTap={isInteractive && !shouldReduceMotion ? { scale: 0.995 } : undefined}
transition={{ type: "spring", stiffness: 260, damping: 28, mass: 0.8 }}
{...props}
/>
);
}
export function BjorkCardHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="bjork-card-header"
className={cn("flex items-start justify-between gap-4", className)}
{...props}
/>
);
}
export function BjorkCardTitle({
className,
...props
}: React.ComponentProps<"h3">) {
return (
<h3
data-slot="bjork-card-title"
className={cn("text-base font-semibold leading-none tracking-[-0.02em] text-[color:var(--bjork-text)]", className)}
{...props}
/>
);
}
export function BjorkCardDescription({
className,
...props
}: React.ComponentProps<"p">) {
return (
<p
data-slot="bjork-card-description"
className={cn("text-sm leading-6 text-[color:var(--bjork-text-soft)]", className)}
{...props}
/>
);
}
export function BjorkCardContent({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="bjork-card-content"
className={cn("mt-5 text-[color:var(--bjork-text-medium)]", className)}
{...props}
/>
);
}
export function BjorkCardFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="bjork-card-footer"
className={cn("mt-5 flex items-center gap-3", className)}
{...props}
/>
);
}
export { bjorkCardVariants };