Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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