Skip to content
Merged
6 changes: 6 additions & 0 deletions apps/www/config/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,12 @@ export const docsConfig: DocsConfig = {
items: [],
label: "New",
},
{
title: "Glyph Matrix",
href: `/docs/components/glyph-matrix`,
items: [],
label: "New",
},
],
},
],
Expand Down
68 changes: 68 additions & 0 deletions apps/www/content/docs/components/glyph-matrix.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: Glyph Matrix
date: 2026-06-12
description: An animated grid of subtly shifting glyphs that reads CSS theme tokens directly, so it works in both light and dark mode.
author: magicui
published: true
---

<ComponentPreview name="glyph-matrix-demo" />

## Installation

<Tabs defaultValue="cli">

<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">

```bash
npx shadcn@latest add @magicui/glyph-matrix
```

</TabsContent>

<TabsContent value="manual">

<Steps>

<Step>Copy and paste the following code into your project.</Step>

<ComponentSource name="glyph-matrix" />

<Step>Update the import paths to match your project setup.</Step>

</Steps>

</TabsContent>

</Tabs>

## Usage

```tsx showLineNumbers
import { GlyphMatrix } from "@/registry/magicui/glyph-matrix"
```

```tsx showLineNumbers
<div className="border-border bg-background h-[400px] w-full overflow-hidden rounded-lg border">
<GlyphMatrix />
</div>
```

## Props

| Prop | Type | Default | Description |
| -------------- | -------- | ---------------- | -------------------------------------------- |
| `glyphs` | `string` | `"01·•+*/\\<>="` | Characters to randomly pick from. |
| `cellSize` | `number` | `14` | Cell size in pixels and font size. |
| `mutationRate` | `number` | `0.04` | Probability a cell mutates each tick. |
| `interval` | `number` | `90` | Tick interval in milliseconds. |
| `className` | `string` | `-` | Classes applied to the canvas element. |
| `fadeBottom` | `number` | `0.6` | Fade strength toward the bottom of the grid. |

## Notes

The component resolves `--foreground` through the browser and normalizes the computed CSS color before drawing. That means Tailwind v4 and shadcn tokens that serialize as `oklch(...)` still render correctly on canvas.
209 changes: 209 additions & 0 deletions apps/www/public/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8829,6 +8829,215 @@ export default function GlobeDemo() {



===== COMPONENT: glyph-matrix =====
Title: Glyph Matrix
Description: An animated grid of subtly shifting glyphs with fade effect and theme support.

--- file: magicui/glyph-matrix.tsx ---
"use client"

import { useEffect, useRef } from "react"

interface GlyphMatrixProps {
/** Characters to randomly pick from */
glyphs?: string
/** Cell size in px (also font size) */
cellSize?: number
/** Probability (0-1) a cell mutates each tick */
mutationRate?: number
/** Tick interval in ms */
interval?: number
/** Optional className for the wrapping canvas */
className?: string
/** Fade out toward bottom (0 = no fade) */
fadeBottom?: number
}

/**
* GlyphMatrix — an animated grid of subtly shifting glyphs.
* Uses semantic tokens (--foreground / --background) so it adapts to
* both light and dark modes automatically.
*/
export function GlyphMatrix({
glyphs = "01·•+*/\\<>=",
cellSize = 14,
mutationRate = 0.04,
interval = 90,
className,
fadeBottom = 0.6,
}: GlyphMatrixProps) {
const canvasRef = useRef<HTMLCanvasElement | null>(null)

useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return

const ctx = canvas.getContext("2d")
if (!ctx) return

let cols = 0
let rows = 0
let cells: string[] = []
let alphas: number[] = []
let raf = 0
let last = 0
let stopped = false

const colorCanvas = document.createElement("canvas")
colorCanvas.width = 1
colorCanvas.height = 1
const colorContext = colorCanvas.getContext("2d")

const readColor = () => {
const probe = document.createElement("span")
probe.style.color = "var(--foreground)"
probe.style.display = "none"
document.body.appendChild(probe)
const computed = getComputedStyle(probe).color
probe.remove()
return computed
}

const parseColor = (value: string) => {
if (!colorContext) return { r: 0, g: 0, b: 0 }

colorContext.fillStyle = "#000"
colorContext.fillStyle = value
const normalized = colorContext.fillStyle
colorContext.fillStyle = normalized
colorContext.fillRect(0, 0, 1, 1)
const pixels = colorContext.getImageData(0, 0, 1, 1).data
const r = pixels[0]
const g = pixels[1]
const b = pixels[2]

return { r, g, b }
}

let fgColor = readColor()

const resize = () => {
const dpr = window.devicePixelRatio || 1
const { clientWidth: w, clientHeight: h } = canvas

canvas.width = w * dpr
canvas.height = h * dpr
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)

cols = Math.ceil(w / cellSize)
rows = Math.ceil(h / cellSize)

cells = new Array(cols * rows)
.fill(0)
.map(() => glyphs[Math.floor(Math.random() * glyphs.length)])
alphas = new Array(cols * rows)
.fill(0)
.map(() => 0.05 + Math.random() * 0.35)

fgColor = readColor()
}

const draw = () => {
const { clientWidth: w, clientHeight: h } = canvas
ctx.clearRect(0, 0, w, h)

const { r, g, b } = parseColor(fgColor)
ctx.font = `${cellSize - 2}px ui-monospace, SFMono-Regular, Menlo, monospace`
ctx.textBaseline = "top"

for (let y = 0; y < rows; y++) {
const fade = fadeBottom > 0 ? 1 - (y / rows) * fadeBottom : 1
for (let x = 0; x < cols; x++) {
const i = y * cols + x
const a = alphas[i] * fade
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`
ctx.fillText(cells[i], x * cellSize, y * cellSize)
}
}
}

const tick = (t: number) => {
if (stopped) return

if (t - last >= interval) {
last = t

const total = cols * rows
const mutations = Math.max(1, Math.floor(total * mutationRate))

for (let n = 0; n < mutations; n++) {
const i = Math.floor(Math.random() * total)
cells[i] = glyphs[Math.floor(Math.random() * glyphs.length)]
alphas[i] = 0.05 + Math.random() * 0.45
}

draw()
}

raf = requestAnimationFrame(tick)
}

resize()
draw()
raf = requestAnimationFrame(tick)

const ro = new ResizeObserver(() => {
resize()
draw()
})
ro.observe(canvas)

// Re-read color when theme changes (class on <html>)
const mo = new MutationObserver(() => {
fgColor = readColor()
draw()
})
mo.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class", "data-theme"],
})

return () => {
stopped = true
cancelAnimationFrame(raf)
ro.disconnect()
mo.disconnect()
}
}, [glyphs, cellSize, mutationRate, interval, fadeBottom])

return (
<canvas
ref={canvasRef}
className={className}
style={{ width: "100%", height: "100%", display: "block" }}
aria-hidden="true"
/>
)
}


===== EXAMPLE: glyph-matrix-demo =====
Title: Glyph Matrix Demo

--- file: example/glyph-matrix-demo.tsx ---
import { GlyphMatrix } from "@/registry/magicui/glyph-matrix"

export default function GlyphMatrixDemo() {
return (
<div className="border-border bg-background relative h-full w-full overflow-hidden rounded-lg border">
<GlyphMatrix
glyphs="01·•+*/\\<>="
cellSize={14}
mutationRate={0.04}
interval={90}
fadeBottom={0.6}
/>
</div>
)
}



===== COMPONENT: grid-pattern =====
Title: Grid Pattern
Description: A background grid pattern made with SVGs, fully customizable using Tailwind CSS.
Expand Down
2 changes: 2 additions & 0 deletions apps/www/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ This file provides LLM-friendly entry points to documentation and examples.
- [Flickering Grid](https://magicui.design/docs/components/flickering-grid): A flickering grid background made with SVGs, fully customizable using Tailwind CSS.
- [Glare Hover](https://magicui.design/docs/components/glare-hover): A diagonal glare on hover using a ::before gradient and CSS variables (angle, size, duration, color).
- [Globe](https://magicui.design/docs/components/globe): An autorotating, interactive, and highly performant globe made using WebGL.
- [Glyph Matrix](https://magicui.design/docs/components/glyph-matrix): An animated grid of subtly shifting glyphs with fade effect and theme support.
- [Grid Pattern](https://magicui.design/docs/components/grid-pattern): A background grid pattern made with SVGs, fully customizable using Tailwind CSS.
- [Hero Video Dialog](https://magicui.design/docs/components/hero-video-dialog): A hero video dialog component.
- [Hexagon Pattern](https://magicui.design/docs/components/hexagon-pattern): A background hexagon pattern made with SVGs, fully customizable using Tailwind CSS.
Expand Down Expand Up @@ -130,6 +131,7 @@ This file provides LLM-friendly entry points to documentation and examples.
- [Marquee Logos](https://github.com/magicuidesign/magicui/blob/main/example/marquee-logos.tsx): Example usage
- [Marquee 3D](https://github.com/magicuidesign/magicui/blob/main/example/marquee-3d.tsx): Example usage
- [Globe Demo](https://github.com/magicuidesign/magicui/blob/main/example/globe-demo.tsx): Example usage
- [Glyph Matrix Demo](https://github.com/magicuidesign/magicui/blob/main/example/glyph-matrix-demo.tsx): Example usage
- [Glare Hover Demo](https://github.com/magicuidesign/magicui/blob/main/example/glare-hover-demo.tsx): Example usage
- [Glare Hover Demo — CTA](https://github.com/magicuidesign/magicui/blob/main/example/glare-hover-demo-cta.tsx): Example usage
- [Glare Hover Demo — Alerts](https://github.com/magicuidesign/magicui/blob/main/example/glare-hover-demo-alert.tsx): Example usage
Expand Down
17 changes: 17 additions & 0 deletions apps/www/public/r/glyph-matrix-demo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "glyph-matrix-demo",
"type": "registry:example",
"title": "Glyph Matrix Demo",
"description": "Example showing an animated grid of subtly shifting glyphs.",
"registryDependencies": [
"@magicui/glyph-matrix"
],
"files": [
{
"path": "registry/example/glyph-matrix-demo.tsx",
"content": "import { GlyphMatrix } from \"@/registry/magicui/glyph-matrix\"\n\nexport default function GlyphMatrixDemo() {\n return (\n <div className=\"border-border bg-background relative h-full w-full overflow-hidden rounded-lg border\">\n <GlyphMatrix\n glyphs=\"01·•+*/\\\\<>=\"\n cellSize={14}\n mutationRate={0.04}\n interval={90}\n fadeBottom={0.6}\n />\n </div>\n )\n}\n",
"type": "registry:example"
}
]
}
14 changes: 14 additions & 0 deletions apps/www/public/r/glyph-matrix.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "glyph-matrix",
"type": "registry:ui",
"title": "Glyph Matrix",
"description": "An animated grid of subtly shifting glyphs with fade effect and theme support.",
"files": [
{
"path": "registry/magicui/glyph-matrix.tsx",
"content": "\"use client\"\n\nimport { useEffect, useRef } from \"react\"\n\ninterface GlyphMatrixProps {\n /** Characters to randomly pick from */\n glyphs?: string\n /** Cell size in px (also font size) */\n cellSize?: number\n /** Probability (0-1) a cell mutates each tick */\n mutationRate?: number\n /** Tick interval in ms */\n interval?: number\n /** Optional className for the wrapping canvas */\n className?: string\n /** Fade out toward bottom (0 = no fade) */\n fadeBottom?: number\n}\n\n/**\n * GlyphMatrix — an animated grid of subtly shifting glyphs.\n * Uses semantic tokens (--foreground / --background) so it adapts to\n * both light and dark modes automatically.\n */\nexport function GlyphMatrix({\n glyphs = \"01·•+*/\\\\<>=\",\n cellSize = 14,\n mutationRate = 0.04,\n interval = 90,\n className,\n fadeBottom = 0.6,\n}: GlyphMatrixProps) {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) return\n\n let cols = 0\n let rows = 0\n let cells: string[] = []\n let alphas: number[] = []\n let raf = 0\n let last = 0\n let stopped = false\n\n const colorCanvas = document.createElement(\"canvas\")\n colorCanvas.width = 1\n colorCanvas.height = 1\n const colorContext = colorCanvas.getContext(\"2d\")\n\n const readColor = () => {\n const probe = document.createElement(\"span\")\n probe.style.color = \"var(--foreground)\"\n probe.style.display = \"none\"\n document.body.appendChild(probe)\n const computed = getComputedStyle(probe).color\n probe.remove()\n return computed\n }\n\n const parseColor = (value: string) => {\n if (!colorContext) return { r: 0, g: 0, b: 0 }\n\n colorContext.fillStyle = \"#000\"\n colorContext.fillStyle = value\n const normalized = colorContext.fillStyle\n colorContext.fillStyle = normalized\n colorContext.fillRect(0, 0, 1, 1)\n const pixels = colorContext.getImageData(0, 0, 1, 1).data\n const r = pixels[0]\n const g = pixels[1]\n const b = pixels[2]\n\n return { r, g, b }\n }\n\n let fgColor = readColor()\n\n const resize = () => {\n const dpr = window.devicePixelRatio || 1\n const { clientWidth: w, clientHeight: h } = canvas\n\n canvas.width = w * dpr\n canvas.height = h * dpr\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0)\n\n cols = Math.ceil(w / cellSize)\n rows = Math.ceil(h / cellSize)\n\n cells = new Array(cols * rows)\n .fill(0)\n .map(() => glyphs[Math.floor(Math.random() * glyphs.length)])\n alphas = new Array(cols * rows)\n .fill(0)\n .map(() => 0.05 + Math.random() * 0.35)\n\n fgColor = readColor()\n }\n\n const draw = () => {\n const { clientWidth: w, clientHeight: h } = canvas\n ctx.clearRect(0, 0, w, h)\n\n const { r, g, b } = parseColor(fgColor)\n ctx.font = `${cellSize - 2}px ui-monospace, SFMono-Regular, Menlo, monospace`\n ctx.textBaseline = \"top\"\n\n for (let y = 0; y < rows; y++) {\n const fade = fadeBottom > 0 ? 1 - (y / rows) * fadeBottom : 1\n for (let x = 0; x < cols; x++) {\n const i = y * cols + x\n const a = alphas[i] * fade\n ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`\n ctx.fillText(cells[i], x * cellSize, y * cellSize)\n }\n }\n }\n\n const tick = (t: number) => {\n if (stopped) return\n\n if (t - last >= interval) {\n last = t\n\n const total = cols * rows\n const mutations = Math.max(1, Math.floor(total * mutationRate))\n\n for (let n = 0; n < mutations; n++) {\n const i = Math.floor(Math.random() * total)\n cells[i] = glyphs[Math.floor(Math.random() * glyphs.length)]\n alphas[i] = 0.05 + Math.random() * 0.45\n }\n\n draw()\n }\n\n raf = requestAnimationFrame(tick)\n }\n\n resize()\n draw()\n raf = requestAnimationFrame(tick)\n\n const ro = new ResizeObserver(() => {\n resize()\n draw()\n })\n ro.observe(canvas)\n\n // Re-read color when theme changes (class on <html>)\n const mo = new MutationObserver(() => {\n fgColor = readColor()\n draw()\n })\n mo.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\", \"data-theme\"],\n })\n\n return () => {\n stopped = true\n cancelAnimationFrame(raf)\n ro.disconnect()\n mo.disconnect()\n }\n }, [glyphs, cellSize, mutationRate, interval, fadeBottom])\n\n return (\n <canvas\n ref={canvasRef}\n className={className}\n style={{ width: \"100%\", height: \"100%\", display: \"block\" }}\n aria-hidden=\"true\"\n />\n )\n}\n",
"type": "registry:ui"
}
]
}
27 changes: 27 additions & 0 deletions apps/www/public/r/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,18 @@
}
]
},
{
"name": "glyph-matrix",
"type": "registry:ui",
"title": "Glyph Matrix",
"description": "An animated grid of subtly shifting glyphs with fade effect and theme support.",
"files": [
{
"path": "registry/magicui/glyph-matrix.tsx",
"type": "registry:ui"
}
]
},
{
"name": "glare-hover",
"type": "registry:ui",
Expand Down Expand Up @@ -2039,6 +2051,21 @@
}
]
},
{
"name": "glyph-matrix-demo",
"type": "registry:example",
"title": "Glyph Matrix Demo",
"description": "Example showing an animated grid of subtly shifting glyphs.",
"registryDependencies": [
"@magicui/glyph-matrix"
],
"files": [
{
"path": "registry/example/glyph-matrix-demo.tsx",
"type": "registry:example"
}
]
},
{
"name": "glare-hover-demo",
"type": "registry:example",
Expand Down
Loading