Glass tables
Five frosted table surfaces that keep real semantic markup and vary only the glass treatment: frost, tint, smoke, crystal and depth. Each is self-contained, sized by density, token-driven, and renders with no props.
Installation
The styled layer is free and installs like any other ai2 component.
Run the following command
npx shadcn@latest add @ai2/table-glassDependencies, the @ai2/tokens theme and the component file are installed together.
Copy the source
components/ui/table-glass.tsx"use client"
import type * as React from "react"
import { cn } from "@/lib/utils"
import { glassDepth } from "@/components/ui/glass"
/* Glass table family: 5 frosted-glass data tables. The difference is only on the
surface - frost, tint, smoke, crystal and depth. Each export renders a complete
<table> with no props too (3 columns / 4 rows by default). Color comes ONLY from
semantic tokens, via alpha color-mix. No animation, pure CSS; self-contained.
The glass surface derives from the glassDepth scale inside @ai2/glass; the
backdrop-blur is never hand-written, so every variant carries the ai2 inset
highlight signature.
Depth map (a wide data panel sitting in the page flow):
Crystal -> sm (4px) clear glass + its own gradient
Frost -> md (8px) the canonical default
Tint -> md (8px) the same glass as Frost, differing in its info hue
Smoke -> lg (16px) dense smoke
Depth -> xl (24px) a floating layered panel
NO GLASS ON GLASS: the blur is ONLY on the shell (the w-full overflow-x-auto
wrapper). thead/tr are only an opaque or semi-transparent COLOR layer and never
take their own backdrop-filter - if they did, the overlapping blur would turn to
mud. */
export type StyledSize = "sm" | "md" | "lg" | "xl"
type Column = { key: string; label: React.ReactNode }
type Row = Record<string, React.ReactNode>
type TableProps = {
className?: string
size?: StyledSize
columns?: Column[]
rows?: Row[]
}
/* A sensible default data set so it renders without props. */
const defaultColumns: Column[] = [
{ key: "name", label: "Name" },
{ key: "role", label: "Role" },
{ key: "status", label: "Status" },
]
const defaultRows: Row[] = [
{ name: "Ada Lovelace", role: "Engineer", status: "Active" },
{ name: "Alan Turing", role: "Researcher", status: "Active" },
{ name: "Grace Hopper", role: "Architect", status: "Away" },
{ name: "Katherine Johnson", role: "Analyst", status: "Active" },
]
/* Hucre yogunlugu: padding + metin olcegi. */
const cell: Record<StyledSize, string> = {
sm: "px-2.5 py-1.5 text-xs",
md: "px-3 py-2 text-sm",
lg: "px-4 py-2.5 text-sm",
xl: "px-5 py-3 text-base",
}
const headCell: Record<StyledSize, string> = {
sm: "px-2.5 py-1.5 text-xs",
md: "px-3 py-2 text-xs",
lg: "px-4 py-2.5 text-sm",
xl: "px-5 py-3 text-sm",
}
const tableBase = "w-full border-collapse text-left align-middle text-foreground"
const headBase = "font-medium text-muted-foreground"
function resolve(columns?: Column[], rows?: Row[]) {
return {
cols: columns && columns.length > 0 ? columns : defaultColumns,
data: rows && rows.length > 0 ? rows : defaultRows,
}
}
type GlassConfig = {
/** Kabuk yuzeyi: kenarlik + cam zemin + blur. */
shell: string
/** Baslik satiri yuzeyi. */
head: string
/** Body row separator plus hover tone. */
row: string
}
/* Shared shell: a glass surface plus a real semantic table. */
function GlassTableShell({
props,
config,
}: {
props: TableProps
config: GlassConfig
}) {
const { className, size = "md", columns, rows } = props
const { cols, data } = resolve(columns, rows)
return (
<div
data-slot="styled-table"
className={cn("w-full overflow-x-auto rounded-xl", config.shell, className)}
>
<table className={tableBase}>
<thead>
<tr className={config.head}>
{cols.map((col) => (
<th key={col.key} scope="col" className={cn(headBase, headCell[size])}>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr
key={i}
className={cn(
"transition-colors duration-(--motion-fast) ease-(--motion-ease) last:border-b-0",
config.row
)}
>
{cols.map((col) => (
<td key={col.key} className={cell[size]}>
{row[col.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
/* Frost: the canonical default glass (md/8px). The scale's bg-background/60 is the same as the old hand-written 60% surface; it now brings the signature along too. */
export function FrostTable(props: TableProps) {
return (
<GlassTableShell
props={props}
config={{
shell: cn(glassDepth.md, "border border-border"),
head: "border-b border-border bg-[color-mix(in_oklab,var(--color-foreground)_5%,transparent)]",
row: "border-b border-border/60 hover:bg-[color-mix(in_oklab,var(--color-foreground)_5%,transparent)]",
}}
/>
)
}
/* Tint: the SAME md step as Frost; what distinguishes it is the info hue, not the blur. (Because primary/brand carries ~0.005 chroma, a "primary tint" would be invisible.) */
export function TintTable(props: TableProps) {
return (
<GlassTableShell
props={props}
config={{
shell: cn(
glassDepth.md,
"border border-[color-mix(in_oklab,var(--color-info)_22%,transparent)]",
"bg-[color-mix(in_oklab,var(--color-info)_8%,transparent)]",
"supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-info)_8%,transparent)]"
),
head: "border-b border-[color-mix(in_oklab,var(--color-info)_22%,transparent)] bg-[color-mix(in_oklab,var(--color-info)_12%,transparent)]",
row: "border-b border-[color-mix(in_oklab,var(--color-info)_14%,transparent)] hover:bg-[color-mix(in_oklab,var(--color-info)_10%,transparent)]",
}}
/>
)
}
/* Smoke: lg (16px). It used to be xl; a wide table sits on a flat page ground, and with nothing to diffuse the 24px only cost a compositor layer. */
export function SmokeTable(props: TableProps) {
return (
<GlassTableShell
props={props}
config={{
shell: cn(
glassDepth.lg,
"border border-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)]",
"bg-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)]",
"supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)]"
),
head: "border-b border-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)] bg-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)]",
row: "border-b border-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)] hover:bg-[color-mix(in_oklab,var(--color-foreground)_12%,transparent)]",
}}
/>
)
}
/* Crystal: sm (4px) - clear glass means little diffusion. Its character is carried by the gradient, which is why the step's fill colour is deliberately pulled to transparent: the gradient is a background-IMAGE and the step's bg is a background-COLOR; both would sit there at once and the panel would read far more opaque than Crystal promises. */
export function CrystalTable(props: TableProps) {
return (
<GlassTableShell
props={props}
config={{
shell: cn(
glassDepth.sm,
"border border-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)]",
"bg-transparent supports-[backdrop-filter]:bg-transparent",
"bg-gradient-to-b from-[color-mix(in_oklab,var(--color-background)_70%,transparent)] to-[color-mix(in_oklab,var(--color-background)_35%,transparent)]"
),
head: "border-b border-[color-mix(in_oklab,var(--color-background)_80%,transparent)] bg-[color-mix(in_oklab,var(--color-background)_75%,transparent)]",
row: "border-b border-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] hover:bg-[color-mix(in_oklab,var(--color-background)_60%,transparent)]",
}}
/>
)
}
/* Depth: xl (24px) - the only variant in the family meant to read as a genuinely
floating layered panel. The old shadow-inner was REMOVED: cn() reduces the
box-shadow group to whichever comes last, so shadow-inner was silently deleting the
glassDepth signature. The layered feel now comes from the opaque (NON-glass) raised
header; the header never takes its own backdrop-filter, otherwise it would be glass
on glass. */
export function DepthTable(props: TableProps) {
return (
<GlassTableShell
props={props}
config={{
shell: cn(
glassDepth.xl,
"border border-border",
"bg-[color-mix(in_oklab,var(--color-muted)_70%,transparent)]",
"supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-muted)_70%,transparent)]"
),
head: "border-b border-border bg-popover shadow-sm",
row: "border-b border-border/50 hover:bg-[color-mix(in_oklab,var(--color-popover)_70%,transparent)]",
}}
/>
)
}Manual installs skip the @ai2/tokens theme, so add the token CSS from the theming guide or the tone colors will be missing.
Variations
5 takes on the same idea. Each is its own export, and every one accepts a size prop (sm, md, lg, xl) aligned to the base Button scale.
Frost
A neutral frosted surface with a medium blur.
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
import { FrostTable } from "@/components/ui/table-glass"
<FrostTable />| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
Tint
A primary-tinted glass surface.
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
import { TintTable } from "@/components/ui/table-glass"
<TintTable />| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
Smoke
A dark smoked glass with a strong blur.
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
import { SmokeTable } from "@/components/ui/table-glass"
<SmokeTable />| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
Crystal
A very translucent top-to-bottom gradient glass.
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
import { CrystalTable } from "@/components/ui/table-glass"
<CrystalTable />| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
Depth
Layered glass with an inner shadow and a raised header.
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
import { DepthTable } from "@/components/ui/table-glass"
<DepthTable />| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
ai2 Glass tables: 5 styled variations on the token system
The ai2 Glass tables are a set of 5 decorative button variations from the styled layer of the @ai2 design system, built around frosted glass data table surfaces. They are free and MIT licensed, and every color comes from a semantic token, so they theme with the rest of ai2 in light and dark.
Motion runs on framer-motion: row hover states run on token CSS transitions. The styled layer is opt-in, so the dependency only lands if you use it; the base components stay lean. Under reduced motion, the hover transitions are disabled and rows stay static.
What is in the ai2 Glass tables?
5 exports in one file: Frost, Tint, Smoke, Crystal and Depth. Each renders a native button and takes a size prop (sm, md, lg, xl) aligned to the base Button. They are separate from the base Button on purpose: the base keeps its clean variant, tone and size axes, while the styled layer carries the effects.
You own the file. Copy the one category file and you have all 5 variations, with no runtime dependency on ai2 itself.
Why use it
- On-system by construction: Every color resolves to an ai2 semantic token, so the buttons follow your theme in light and dark with no extra work.
- Effect without the sprawl: The decorations live in a dedicated styled file, so the base Button keeps its clean, predictable API.
- Accessible and honest: Each renders a real button element, keeps a visible focus ring, and respects prefers-reduced-motion.
Features
- Token-driven color: No hardcoded hex or oklch; the look recolors with your theme tokens.
- framer-motion: row hover states run on token CSS transitions.
- Reduced-motion aware: Under prefers-reduced-motion, the hover transitions are disabled and rows stay static.
- Size aligned to the base: Every variation takes sm, md, lg and xl matching the base Button height scale, so styled and base buttons line up in a row.
Production tips
- Use it for emphasis, not everywhere: Styled buttons draw the eye. Reserve them for the one action you want people to take on a screen, and use the base Button for the rest.
- Keep labels as verbs: The decoration adds weight, so a clear action label keeps the button scannable.
- Pick one variation per surface: The variations share a family; using two different ones in the same view competes for attention.
Works with the rest of ai2
The Glass tables sit alongside the base Button and the rest of the @ai2 registry. They share the same token file, so a styled action next to a base button or a badge stays visually consistent in both modes.