DepthText is a lightweight, dependency-free JavaScript library that creates smooth multi-layer 3D text with depth, parallax, and interactive rotation.
Designed for high performance, accessibility, and modern UX motion guidelines.
It is the spiritual successor of ztext.js, but rewritten from scratch with a cleaner API, better performance, and a modern ES module architecture.
- 🌀 True 3D layered depth using CSS
transform-style: preserve-3d - 🖱️ Interactive rotation (auto, pointer, scroll…)
- 🎚️ Configurable number of layers
- 🎨 Optional fade effect across layers
- ⚡ GPU-optimized and jank-free
- 🦾 Accessibility-friendly (
aria-hidden, reduced-motion support) - 🔥 No dependencies, only 4–6 KB minified
- 📦 Works with bundlers, ES modules, and browsers
- 🖼️ Supports images, SVGs, and emojis within text content
- 🎭 Custom CSS classes for advanced styling control
- 🎨 Per-layer styling with
layerClassMapand auto-generated classes - 🔄 Dynamic updates with
.update()method
npm install depthtext<!-- ESM --><scripttype="module">import{DepthTextify}from"https://cdn.jsdelivr.net/npm/depthtext@latest/dist/depthtext.mjs";DepthTextify();</script><!-- Global/IIFE (classic script tag) --><scriptsrc="https://cdn.jsdelivr.net/npm/depthtext@latest/dist/depthtext.global.js"></script><script>DepthText.DepthTextify();</script><!-- ESM --><scripttype="module">import{DepthTextify}from"https://unpkg.com/depthtext@latest/dist/depthtext.mjs";DepthTextify();</script><!-- Global/IIFE --><scriptsrc="https://unpkg.com/depthtext@latest/dist/depthtext.global.js"></script><script>DepthText.DepthTextify();</script>import{DepthTextify}from"depthtext";<h1class="depthtext" data-depth="2rem" data-depth-event="pointer">DepthText Rocks</h1>import{DepthTextify}from"depthtext";DepthTextify();// Enhances all elements with [data-depth]DepthText can be configured using either:
- HTML data attributes
- or JavaScript options
| Attribute | Type | Default | Description |
|---|---|---|---|
data-depth | string | 1rem | Z-axis distance between layers (e.g., "2rem", "20px") |
data-depth-layers | number | 10 | Number of 3D layers (1-40, recommended ≤25 for performance) |
data-depth-direction | string | both | Layer direction: both, forwards, backwards |
data-depth-event | string | none | Interaction: none, pointer, scroll, scrollX, scrollY |
data-depth-event-rotation | string | 30deg | Max rotation angle on interaction (e.g., "45deg", "0.5rad") |
data-depth-event-direction | string | default | Rotation direction: default, reverse |
data-depth-fade | boolean | false | Fade layers as depth increases |
data-depth-perspective | string | 500px | CSS perspective value |
data-depth-engaged | boolean | true | Enable/disable the effect |
data-depth-add-class | string | "" | Custom CSS class(es) to add to ALL layers |
data-depth-engaged to enable/disable, NOT data-depth alone.
import{DepthTextInstance}from"depthtext";constinstance=newDepthTextInstance(element,{depth: "1rem",// Z-distance between layerslayers: 10,// Number of layersdirection: "both",// "both" | "forwards" | "backwards"event: "pointer",// "none" | "pointer" | "scroll" | "scrollX" | "scrollY"eventRotation: "30deg",// Max tilt angleeventDirection: "default",// "default" | "reverse"fade: false,// Enable opacity fadeperspective: "500px",// CSS perspectiveengaged: true,// Enable/disable effectaddClass: "my-class",// Custom class for ALL layerslayerClassMap: ["front","middle","back"],// Custom class per layer (NEW in v1.2.0)});// Dynamically update options (NEW in v1.2.0)instance.update({depth: "2rem",event: "scroll",});// Cleanupinstance.destroy();Each layer automatically receives a unique class:
<!-- 3 layers example --><spanclass="depthtext-layer depthtext-layer-0">...</span><spanclass="depthtext-layer depthtext-layer-1">...</span><spanclass="depthtext-layer depthtext-layer-2">...</span>Target specific layers with CSS:
.depthtext-layer-0 {
/* Front layer */
}
.depthtext-layer-1 {
/* Middle layer */
}
.depthtext-layer-2 {
/* Back layer */
}Map custom classes to individual layers by index:
newDepthTextInstance(element,{layers: 5,layerClassMap: ["front","mid-front","center","mid-back","back"],});Result:
<spanclass="depthtext-layer depthtext-layer-0 front">...</span><spanclass="depthtext-layer depthtext-layer-1 mid-front">...</span><spanclass="depthtext-layer depthtext-layer-2 center">...</span><spanclass="depthtext-layer depthtext-layer-3 mid-back">...</span><spanclass="depthtext-layer depthtext-layer-4 back">...</span>Rainbow gradient example:
newDepthTextInstance(element,{layers: 7,layerClassMap: ["red","orange","yellow","green","blue","indigo","violet"],});.red {
color:#ff0000;
}
.orange {
color:#ff7f00;
}
.yellow {
color:#ffff00;
}
.green {
color:#00ff00;
}
.blue {
color:#0000ff;
}
.indigo {
color:#4b0082;
}
.violet {
color:#9400d3;
}newDepthTextInstance(element,{layers: 10,layerClassMap: ["special-0","special-1","special-2"],// Layers 3-9 will only have: depthtext-layer depthtext-layer-N});<h1data-depth="1rem" data-depth-add-class="gradient-text">Stylized Text</h1>.gradient-text {
background:linear-gradient(45deg,#ff6b6b,#4ecdc4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}Multiple classes:
newDepthTextInstance(element,{addClass: "gradient-text shadow-effect animated",});Because layers are clones sharing the same text formatting context, color inheritance can be tricky.
❌ This WON'T work reliably:
.depthtext-layer-0 {
color: blue;
}
.depthtext-layer-1 {
color: red;
}✅ Use the universal selector instead:
.depthtext-layer-0* {
color: blue !important;
}
.depthtext-layer-1* {
color: red !important;
}Or add a global reset:
.depthtext-layer>* {
color: inherit !important;
}This ensures each layer's content truly inherits its parent layer's color.
/* Neon glow effect */
.neon-glow {
color:#00ffff;
text-shadow:0010px#00ffff,0020px#00ffff,0030px#00ffff;
}
/* Metallic gradient */
.metallic {
background:linear-gradient(90deg,#c0c0c0,#e8e8e8,#c0c0c0);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Rainbow animation */@keyframes rainbow {
0%,100% {
color:#ff0000;
}
16% {
color:#ff7f00;
}
33% {
color:#ffff00;
}
50% {
color:#00ff00;
}
66% {
color:#0000ff;
}
83% {
color:#8b00ff;
}
}
.rainbow-text {
animation: rainbow 3s linear infinite;
}
/* Per-layer depth-based opacity */
.depthtext-layer-0 {
opacity:1;
}
.depthtext-layer-1 {
opacity:0.9;
}
.depthtext-layer-2 {
opacity:0.8;
}
.depthtext-layer-3 {
opacity:0.7;
}Change options after initialization without recreating the instance:
constinstance=newDepthTextInstance(element,{depth: "1rem",event: "pointer",});// Later, change behaviorinstance.update({depth: "2rem",event: "scroll",layers: 15,});Perfect for:
- Responsive designs (change depth on mobile)
- Interactive controls (user preferences)
- State-based animations
Auto-enhance all elements with [data-depth] attribute:
<h1data-depth="2rem" data-depth-event="pointer">Auto-enhanced</h1><h2data-depth="1rem" data-depth-layers="5">Another one</h2>import{DepthTextify}from"depthtext";// Initialize all [data-depth] elementsDepthTextify();// Or with custom selectorDepthTextify(".custom-selector");// Or with default optionsDepthTextify({depth: "2rem",event: "pointer",});DepthText is designed to remain invisible to assistive technologies:
- Wrappers are automatically marked with
aria-hidden="true" - Motion reacts to
prefers-reduced-motion: reduce - Original text remains intact and readable in source
- Screen readers only see the original content
<scriptsrc="dist/depthtext.global.js"></script><script>// DepthText is available globallyDepthText.DepthTextify();</script>DepthText is framework-agnostic. You can use it with React, Vue, Angular, Svelte, etc., by instantiating DepthTextInstance on a mounted DOM element.
import{useEffect,useRef}from"react";import{DepthTextInstance}from"depthtext";exportdefaultfunctionDepthComponent(){consttextRef=useRef(null);useEffect(()=>{if(!textRef.current)return;constdt=newDepthTextInstance(textRef.current,{layers: 10,depth: "1rem",event: "pointer",layerClassMap: ["front","mid","back"],// Per-layer styling});// Cleanup on unmountreturn()=>dt.destroy();},[]);return<h1ref={textRef}>DepthText in React</h1>;}<scriptsetup>import{onMounted,onUnmounted,ref}from"vue";import{DepthTextInstance}from"depthtext";consttextRef=ref(null);letdt=null;onMounted(()=>{if(textRef.value){dt=newDepthTextInstance(textRef.value,{layers: 10,event: "pointer",addClass: "custom-style",layerClassMap: ["layer-a","layer-b","layer-c"],});}});onUnmounted(()=>{if(dt)dt.destroy();});</script><template><h1ref="textRef">DepthText in Vue</h1></template>import{Component,ElementRef,ViewChild,AfterViewInit,OnDestroy}from"@angular/core";import{DepthTextInstance}from"depthtext";
@Component({selector: "app-depth-text",template: "<h1 #depthText>DepthText in Angular</h1>",})exportclassDepthTextComponentimplementsAfterViewInit,OnDestroy{
@ViewChild("depthText")textRef!: ElementRef;privatedt?: DepthTextInstance;ngAfterViewInit(){this.dt=newDepthTextInstance(this.textRef.nativeElement,{layers: 10,event: "pointer",layerClassMap: ["front","middle","back"],});}ngOnDestroy(){this.dt?.destroy();}}DepthText works with various content types:
- Text content (including unicode, special characters)
- Emojis (😊, 🚀, ❤️, etc.)
- Inline SVG (
<svg>...</svg>) - Images (
<img src="...">) - Mixed content (text + images + SVGs together)
<h1class="depthtext" data-depth="1rem" data-depth-event="pointer" data-depth-add-class="colorful">
Hello World! 🚀
<svgwidth="30" height="30"><circlecx="15" cy="15" r="10" fill="blue" /></svg><imgsrc="logo.png" width="40" alt="Logo" /></h1>- Images should be loaded before initialization for best results
- Very large images may impact performance
- External SVGs (
<img src="icon.svg">) work like regular images
- DepthText uses CSS transforms; parent elements must not flatten 3D contexts
- Avoid nested DepthText unless you understand
transform-style: preserve-3d - Images: For complex layouts, consider using
background-imageon wrapper elements - Performance: Very high layer counts (>25) with large images may impact performance
- Color styling: Use
.depthtext-layer-N * { color: ... }for reliable per-layer coloring
Pull requests are welcome!
If you add a major feature, please include documentation updates.
MIT License – free to use in commercial and open-source projects.
Created by MobiWise.
A modern reimagining of the classic ztext.js.
