Niels Leenheer has done something unreasonable. Something beautiful and absurd in equal measure. He built a working 3D renderer for the original Doom — not with JavaScript, not with WebGL, not with Canvas — but with CSS alone. No scripting. No logic layer. Just stylesheets doing things stylesheets were never meant to do.
The project, which Leenheer detailed in a comprehensive technical writeup on his personal site, isn’t a toy demo or a static mockup. It renders actual Doom levels in real time, with perspective-correct walls, floors, ceilings, texture mapping, and even lighting — all computed through CSS custom properties, calc() functions, and a rendering pipeline built entirely from declarative style rules. The browser’s layout engine becomes the game engine. The cascade becomes the control flow.
It is, by any rational measure, an act of creative engineering madness. And it tells us something real about where CSS has quietly arrived as a technology.
Leenheer, a Dutch developer known for pushing browser technologies to their limits (he previously built HTML5test.com, a widely used browser compatibility testing tool), describes the project not as a port of Doom but as a CSS-powered 3D renderer that happens to use Doom’s level data. The distinction matters. He didn’t shoehorn game logic into stylesheets. He built a raycasting engine — the same fundamental rendering technique id Software used in 1993 — using nothing but the styling language that was originally designed to set fonts and margins.
The technical details are staggering.
Raycasting, for the uninitiated, works by casting a ray from the player’s viewpoint for each vertical column of pixels on the screen. Each ray travels outward until it hits a wall. The distance to that wall determines how tall the wall slice should be drawn — closer walls are taller, farther walls are shorter. This creates the illusion of 3D from a 2D map. It’s the same principle behind Wolfenstein 3D and the original Doom, though Doom added variable-height walls, non-orthogonal geometry, and floors and ceilings that Wolfenstein lacked.
Leenheer’s implementation performs all of this math inside CSS. Custom properties (CSS variables) store the player’s position, angle, and the map data. The calc() function — designed for simple arithmetic like width: calc(100% - 20px) — is pushed into service for trigonometric calculations, distance computations, and perspective projection. CSS doesn’t natively support sine and cosine, so Leenheer uses the relatively new CSS sin(), cos(), tan(), and atan2() trigonometric functions that were added to the CSS Values and Units Module Level 4 specification. These functions, which only achieved broad browser support in the last couple of years, are the linchpin that makes the entire project possible.
Without them, you’d be stuck approximating trig with polynomial hacks inside calc(). With them, you can do real raycasting math. The timing isn’t accidental — this project couldn’t have existed even three years ago.
Each vertical column of the display is a separate HTML element. Its height, position, color, and texture offset are all determined by CSS calculations that trace a ray from the virtual camera into the map. The walls are rendered as stretched background images, with background-position and background-size doing the work of a texture mapper. Lighting is handled by adjusting opacity or applying color filters based on distance — the same depth-cue technique that gave the original Doom its moody atmosphere.
Floors and ceilings use a different approach. As Leenheer explains, floor and ceiling rendering in a raycaster requires per-pixel distance calculations rather than per-column calculations, which dramatically increases computational complexity. He addresses this with CSS gradients and careful use of perspective transforms, creating the visual impression of receding horizontal surfaces without actually calculating each pixel individually. It’s an approximation, but a convincing one.
The Browser as an Unwitting Game Console
What makes this project technically fascinating — and somewhat alarming — is what it reveals about the computational power now embedded in CSS. The language has grown from a simple presentation layer into something that can express conditional logic (through container queries and @supports), perform complex mathematics (through calc, min, max, clamp, and trigonometric functions), maintain state (through custom properties and the new @property rule with typed values), and even respond to user input (through :hover, :checked, and other pseudo-classes).
Leenheer’s Doom renderer uses :hover states on invisible overlay elements to detect where the user is pointing, effectively creating mouse-look controls without JavaScript. Checkbox hacks — a well-known CSS trick where hidden checkboxes combined with the :checked pseudo-class toggle visual states — handle discrete actions. The player moves by interacting with HTML elements whose state changes propagate through CSS variable inheritance to update the entire rendering pipeline.
It’s Turing-incomplete, technically. CSS can’t loop. It can’t branch arbitrarily. It doesn’t have general-purpose variable assignment. But within its constraints, it can do a shocking amount of computation per frame, because modern browsers have heavily optimized their CSS engines for performance. The layout engine processes thousands of property calculations per frame at 60fps because it has to — that’s what modern web design demands. Leenheer is simply redirecting that computational budget toward raycasting.
The performance, according to Leenheer’s writeup, is surprisingly acceptable. Not fast. Not 60fps. But functional enough to walk through Doom’s E1M1 and recognize the geometry, the textures, the spatial relationships. The browser churns through the calculations, the GPU composites the layers, and something that looks recognizably like Doom appears on screen. Rendered by a stylesheet.
This sits within a long tradition of developers abusing CSS for purposes its creators never imagined. People have built entire games in CSS — from simple platformers to chess. Ben Evans created a working calculator. Diana Smith painted photorealistic portraits using nothing but styled div elements. But a real-time 3D raycasting engine represents a qualitative leap. Previous CSS stunts were mostly static or involved simple state machines. This is continuous, real-time, mathematically intensive rendering.
And it works because CSS itself has changed. The addition of trigonometric functions in 2023-2024 was the critical enabler, but the broader trend has been building for years. Custom properties landed in 2017. The @property rule, which allows developers to define typed custom properties with initial values and inheritance behavior, reached baseline support in 2024. Container queries shipped in 2023. Each addition was justified by legitimate design use cases — responsive typography, component-scoped styling, design tokens. But collectively, they’ve created a declarative language with genuine computational expressiveness.
The CSS Working Group at the W3C has been aware of this trajectory. Proposals for CSS if() — a proper inline conditional function — are under active discussion. Scroll-driven animations, which shipped in Chrome and are coming to other browsers, allow CSS to respond to scroll position without JavaScript. The line between styling and programming continues to blur.
Some developers celebrate this. More capability in CSS means less JavaScript, which means better performance, better accessibility, and fewer points of failure. Others worry about maintainability. CSS was designed to be declarative and predictable. The more computational power it gains, the more it can be used in ways that are difficult to debug, difficult to read, and difficult to hand off to another developer. A 3D raycasting engine in CSS is a proof of concept that doubles as a cautionary tale.
Leenheer himself seems aware of the tension. His writeup is meticulous in its technical detail but playful in its framing. The title — “CSS is Doomed” — is a pun, obviously, but also a knowing wink at the perennial debate about whether CSS is becoming too complex. He isn’t arguing that anyone should build games in CSS. He’s demonstrating what’s possible, and letting the implications speak for themselves.
The project also serves as an unintentional benchmark for browser CSS performance. Different browsers handle the heavy calc() chains and trigonometric functions with varying degrees of efficiency. Chrome’s Blink engine and Firefox’s Gecko engine produce noticeably different frame rates, which highlights how much CSS rendering performance depends on engine-specific optimizations that most developers never think about. Safari’s WebKit, with its historically conservative approach to new CSS features, presents its own quirks.
So where does this leave us? A developer in the Netherlands has rendered Doom in CSS. It’s a stunt. It’s art. It’s a technical achievement that required genuine expertise in both CSS internals and 3D rendering mathematics. And it’s a signal — perhaps the clearest one yet — that CSS has grown into something its inventors never anticipated.
Tim Berners-Lee and Håkon Wium Lie proposed CSS in 1994 as a way to separate document structure from presentation. Thirty-one years later, it’s rendering the hellscapes of Phobos. The cascade, it turns out, contains multitudes.


WebProNews is an iEntry Publication