How to add a cool ASCII animated hero section to your website (step-by-step guide)

A step-by-step guide to building a 3D ASCII animated hero section for your Next.js website using Three.js and React Three Fiber. Learn how to set it up, drop in your own 3D model, and tweak the ASCII effect to match your vibe.

Builds7 min read

Your hero section is the first thing people see. You get maybe 3 seconds before someone decides if your site feels generic or worth exploring. A wall of text and a stock image won't cut it. Especially if you're building a portfolio, a dev tool landing page, or anything where you want to get this feeling:

"wooow this is cool, let me keep this tab open for a sec"

Live demo →

GitHub repo →

This build got much love on Twitter when I first shared it. Here's the original post:

Egor
Egor@0gurex·Feb 11, 2026

I've built a free Next.js boilerplate that turns 3D models into animated ASCII art for your hero section. Open source, beginner friendly (Three.js, React Three Fiber, GLSL shaders). Here's a step-by-step guide https://medium.com/p/d4070e45e2c8 Demo https://nextjs-ascii-hero.vercel.app

In this guide, I'll walk you through how to build a hero section that renders a 3D model as animated ASCII art. It rotates, it reacts to your mouse, it has that retro CRT glow.

I'll explain how to set it up, drop in your own 3D model, and tweak the ASCII effect to match your vibe. No 3D experience required.

Quick intro: what's Three.js and how to make 3D render on your website

Browsers are great at showing text and images, but 3D? That's a different story. Rendering a rotating 3D object requires a lot of behind-the-scenes work that browsers don't do natively.

Three.js solves this. It's a JavaScript library that handles all the 3D complexity for you. And React Three Fiber (R3F) lets you use it as regular React components, so if you know React, you're already good to go.

This boilerplate uses both. You drop in a 3D model, and the scene renders it as animated ASCII art. You don't need to understand how 3D rendering works. Just follow the steps below.

Step 1: install and launch

Clone the repo and get it running:

git clone https://github.com/egorshest/webgl-ascii-hero.git
cd webgl-ascii-hero
npm install
npm run dev

Open http://localhost:3000. You should see the hero section with a rotating ASCII 3D model right away.

That's it. Three commands and you've got a working 3D ASCII hero.

The project is a standard Next.js app with TypeScript. The structure looks like this:

webgl-ascii-hero/
├── app/
│   ├── layout.tsx
│   ├── page.tsx
│   └── globals.css
├── components/
│   ├── hero.tsx            # Hero layout
│   ├── effect-scene.tsx    # 3D scene + camera + lighting
│   └── ascii-effect.tsx    # ASCII post-processing shader
└── public/
    └── models/
        └── user-model.glb  # Your 3D model goes here

Most of your customization happens in effect-scene.tsx. That's where the model, camera, lighting, and ASCII effect all come together.

Step 2: upload your 3D model

The repo ships with a demo .glb model so you can see it working immediately. But you should replace it with your own.

Where to find free 3D models:

  • Sketchfab filter by "Downloadable" and CC0 license. Huge library.
  • Poly Haven all CC0, high quality, no strings attached.
  • glTF Sample Models good for testing.
  • Or export your own from Blender.

How to add it:

  1. Download a .glb file
  2. Drop it into public/models/ and name it user-model.glb
  3. Done. The code already references /models/user-model.glb so no code changes needed.

One important thing: keep the model file small. Anything over 5–10MB will slow down your page load noticeably. The ASCII effect hides a lot of detail anyway, so you don't need a super high-poly model. Low-poly or medium-poly works great because the shader does the heavy lifting visually.

If your model looks too big or too small after loading, adjust the scale in components/effect-scene.tsx:

<UserModel scale={8} />  // Increase or decrease this number

Step 3: configure camera and scene

The scene setup in effect-scene.tsx controls how your model is positioned, lit, and viewed.

Camera: The boilerplate uses a perspective camera. You can adjust the field of view and position to frame your model the way you want. If you're switching to a very different model shape (say, from a bust to a car), you'll likely need to tweak the camera position so the model is nicely centered in the canvas.

Lighting: The default setup uses ambient light plus two directional lights. This matters more than you'd think. The ASCII shader converts pixel brightness to characters, so your lighting directly affects how the ASCII art looks. More contrast = more readable ASCII.

Here's the default lighting setup:

<ambientLight intensity={0.08} />
<directionalLight position={[2, 3.5, 6]} intensity={6} />
<directionalLight position={[-2, 1.5, 4]} intensity={0.35} />

Play with the intensity and position values. A model that's lit too flatly will look muddy in ASCII. A model with strong directional light will have sharp, legible character edges. The sweet spot depends on your model's geometry.

Interactivity: The model supports drag-to-rotate and hover-to-zoom out of the box. Mouse position also affects the glow effect, so the hero reacts to user movement. This all works without any extra config.

Step 4: configure the ASCII styling

This is where you tweak the styling to make it look really cool. The AsciiEffect component in effect-scene.tsx accepts several props that control the look:

<AsciiEffect
  cellSize={9}              // Size of each ASCII "pixel"
  invert={true}             // Invert brightness mapping
  color={true}              // Color or monochrome
  characterSet="terminal"   // Which characters to use
  volumeShading={true}      // Adds depth perception
  tintColor="#917AFF"       // Tint the whole effect
  postfx={{
    contrastAdjust: 1.8,    // Crank up for sharper characters
    brightnessAdjust: 0,    // Shift overall brightness
  }}
/>

Here's what actually matters:

cellSize is the big one. Smaller values = more characters = more detail = more "resolution." Larger values = chunkier, more stylized look. Start with 9 and adjust. If your model has fine details you want to preserve, go lower (6-8). If you want a bold, abstract look, go higher (12-16).

tintColor controls the overall color of the effect. Change this to match your brand. The default purple (#917AFF) looks great on dark backgrounds, but swap it to whatever fits your site. Greens for a hacker vibe, white for minimal, warm orange for creative portfolios.

characterSet controls which ASCII characters represent different brightness levels. "terminal" gives you classic terminal characters. The character set directly affects the mood. Some sets feel more technical, others more artistic.

contrastAdjust creates sharper separation between light and dark areas of your model when set higher. This makes the ASCII art more legible. If your model looks washed out in ASCII, bump this up.

invert flips which characters map to light vs dark. Depending on your background color and model, one direction will look much better than the other. Just try both.

The key insight: the ASCII effect is essentially a brightness-to-character mapping. Every tweak you make to lighting, contrast, cell size, and character set changes how that mapping plays out visually. There's no single "correct" setting. It depends on your model's geometry and your aesthetic preferences. Spend 10 minutes experimenting and you'll find something that looks great.

Common issues (and how to fix them)

A few things that tripped me up during development:

Object too large for the canvas

If your 3D model overflows the canvas or looks cropped, the model's scale is too large relative to the camera's field of view. Two fixes:

  1. Reduce the scale prop on your model component
  2. Move the camera further back (increase the z-position)

Start with scale adjustments. It's usually the quicker fix.

React rendering side effects with canvas

React Three Fiber's <Canvas> component has its own render loop that runs outside React's normal reconciliation. This means if your parent component re-renders frequently (state changes, context updates), it can cause the 3D scene to flicker or reset.

The fix: make sure your 3D scene components are memoized properly. If you have state that changes often in the parent, don't let it propagate into the Canvas unless the scene actually needs that data.

Multiple canvases conflicting

If you're using two <Canvas> components on the same page (say, one for the hero ASCII effect and another for something else), you need to isolate them properly. Each canvas should be its own self-contained component with its own scene configuration. Don't share state or refs between them.

The issue is that WebGL contexts can interfere with each other, especially with post-processing effects. Keep each canvas isolated in its own component tree with its own EffectComposer, and you'll avoid weird rendering glitches.

Wrapping up

Adding a 3D ASCII hero section is a small thing, but it's the kind of micro-improvement that makes your site memorable. People notice when a website has personality. They stop scrolling. They play with it. That's exactly what your hero section should do.

I hope this guide was useful. If you build something cool with it, I'd love to see it. Drop a comment or share it. If you found this helpful, buy me a coffee ☕.

Fork the repo on GitHub →

See the live demo →

Happy building!

Egor Shesternin - Senior product manager