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
69 changes: 69 additions & 0 deletions apps/www/content/docs/components/glyph-matrix.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
title: Glyph Matrix
date: 2026-06-12
description: An animated grid of subtly shifting glyphs on a canvas, with a theme-aware color driven by the consumer.
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. |
| `color` | `string` | `"#6B7280"` | Glyph color (any CSS color). |

## Notes

The component draws with the `color` prop, which accepts any CSS color (hex, `rgb()`, `hsl()`, `oklch()`, ...) and is normalized through canvas before drawing. For light/dark support, drive `color` from the consumer — e.g. with `next-themes` as shown in the demo above.
206 changes: 206 additions & 0 deletions apps/www/public/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8829,6 +8829,212 @@ 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"

import { cn } from "@/lib/utils"

interface GlyphMatrixProps extends React.HTMLAttributes<HTMLCanvasElement> {
/** 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
/** Fade out toward bottom (0 = no fade) */
fadeBottom?: number
/** Glyph color (any CSS color). Pass a theme-aware value from the consumer. */
color?: string
}

/**
* GlyphMatrix — an animated grid of subtly shifting glyphs.
* Pass a `color` prop (e.g. driven by next-themes) to adapt it to
* light and dark modes.
*/
export function GlyphMatrix({
glyphs = "01·•+*/\\<>=",
cellSize = 14,
mutationRate = 0.04,
interval = 90,
className,
fadeBottom = 0.6,
color = "#6B7280",
style,
...props
}: GlyphMatrixProps) {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
// Current glyph color as RGBA (a in 0-1). Kept in a ref so a color change
// (e.g. theme toggle) recolors the next frame without restarting the
// animation. Defaults to #6B7280.
const rgbaRef = useRef({ r: 107, g: 114, b: 128, a: 1 })

// Resolve the CSS color string to RGBA (handles hex, rgb, hsl, oklch, ...).
useEffect(() => {
const probe = document.createElement("canvas")
probe.width = 1
probe.height = 1
const probeCtx = probe.getContext("2d")
if (!probeCtx) return
// Seed with the default so an invalid color falls back to it: the 2d
// context keeps the previous fillStyle when assigned an invalid value
// instead of silently turning black.
probeCtx.fillStyle = "#6B7280"
probeCtx.fillStyle = color
probeCtx.fillRect(0, 0, 1, 1)
const [r, g, b, a] = probeCtx.getImageData(0, 0, 1, 1).data
rgbaRef.current = { r, g, b, a: a / 255 }
}, [color])

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 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)
}

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

ctx.font = `${cellSize - 2}px ui-monospace, SFMono-Regular, Menlo, monospace`
ctx.textBaseline = "top"

const { r, g, b, a: colorAlpha } = rgbaRef.current
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 * colorAlpha
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)

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

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


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

--- file: example/glyph-matrix-demo.tsx ---
"use client"

import { useEffect, useState } from "react"
import { useTheme } from "next-themes"

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

export default function GlyphMatrixDemo() {
const { resolvedTheme } = useTheme()
// Start neutral so the first frame is visible in both themes, then adopt
// the resolved theme color once it's known.
const [color, setColor] = useState("#6B7280")

useEffect(() => {
if (!resolvedTheme) return
setColor(resolvedTheme === "dark" ? "#ffffff" : "#000000")
}, [resolvedTheme])

return (
<div className="border-border bg-background relative h-[400px] w-full overflow-hidden rounded-lg border">
<GlyphMatrix
glyphs="01·•+*/\<>="
cellSize={14}
mutationRate={0.04}
interval={90}
fadeBottom={0.6}
color={color}
/>
</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
20 changes: 20 additions & 0 deletions apps/www/public/r/glyph-matrix-demo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$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.",
"dependencies": [
"next-themes"
],
"registryDependencies": [
"@magicui/glyph-matrix"
],
"files": [
{
"path": "registry/example/glyph-matrix-demo.tsx",
"content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { useTheme } from \"next-themes\"\n\nimport { GlyphMatrix } from \"@/registry/magicui/glyph-matrix\"\n\nexport default function GlyphMatrixDemo() {\n const { resolvedTheme } = useTheme()\n // Start neutral so the first frame is visible in both themes, then adopt\n // the resolved theme color once it's known.\n const [color, setColor] = useState(\"#6B7280\")\n\n useEffect(() => {\n if (!resolvedTheme) return\n setColor(resolvedTheme === \"dark\" ? \"#ffffff\" : \"#000000\")\n }, [resolvedTheme])\n\n return (\n <div className=\"border-border bg-background relative h-[400px] 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 color={color}\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\nimport { cn } from \"@/lib/utils\"\n\ninterface GlyphMatrixProps extends React.HTMLAttributes<HTMLCanvasElement> {\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 /** Fade out toward bottom (0 = no fade) */\n fadeBottom?: number\n /** Glyph color (any CSS color). Pass a theme-aware value from the consumer. */\n color?: string\n}\n\n/**\n * GlyphMatrix — an animated grid of subtly shifting glyphs.\n * Pass a `color` prop (e.g. driven by next-themes) to adapt it to\n * light and dark modes.\n */\nexport function GlyphMatrix({\n glyphs = \"01·•+*/\\\\<>=\",\n cellSize = 14,\n mutationRate = 0.04,\n interval = 90,\n className,\n fadeBottom = 0.6,\n color = \"#6B7280\",\n style,\n ...props\n}: GlyphMatrixProps) {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n // Current glyph color as RGBA (a in 0-1). Kept in a ref so a color change\n // (e.g. theme toggle) recolors the next frame without restarting the\n // animation. Defaults to #6B7280.\n const rgbaRef = useRef({ r: 107, g: 114, b: 128, a: 1 })\n\n // Resolve the CSS color string to RGBA (handles hex, rgb, hsl, oklch, ...).\n useEffect(() => {\n const probe = document.createElement(\"canvas\")\n probe.width = 1\n probe.height = 1\n const probeCtx = probe.getContext(\"2d\")\n if (!probeCtx) return\n // Seed with the default so an invalid color falls back to it: the 2d\n // context keeps the previous fillStyle when assigned an invalid value\n // instead of silently turning black.\n probeCtx.fillStyle = \"#6B7280\"\n probeCtx.fillStyle = color\n probeCtx.fillRect(0, 0, 1, 1)\n const [r, g, b, a] = probeCtx.getImageData(0, 0, 1, 1).data\n rgbaRef.current = { r, g, b, a: a / 255 }\n }, [color])\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 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\n const draw = () => {\n const { clientWidth: w, clientHeight: h } = canvas\n ctx.clearRect(0, 0, w, h)\n\n ctx.font = `${cellSize - 2}px ui-monospace, SFMono-Regular, Menlo, monospace`\n ctx.textBaseline = \"top\"\n\n const { r, g, b, a: colorAlpha } = rgbaRef.current\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 * colorAlpha\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 return () => {\n stopped = true\n cancelAnimationFrame(raf)\n ro.disconnect()\n }\n }, [glyphs, cellSize, mutationRate, interval, fadeBottom])\n\n return (\n <canvas\n ref={canvasRef}\n className={cn(\"pointer-events-none\", className)}\n style={{ width: \"100%\", height: \"100%\", display: \"block\", ...style }}\n aria-hidden=\"true\"\n {...props}\n />\n )\n}\n",
"type": "registry:ui"
}
]
}
30 changes: 30 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,24 @@
}
]
},
{
"name": "glyph-matrix-demo",
"type": "registry:example",
"title": "Glyph Matrix Demo",
"description": "Example showing an animated grid of subtly shifting glyphs.",
"dependencies": [
"next-themes"
],
"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
Loading