Show HN: X86CSS – An x86 CPU emulator written in CSS

2026-02-242:2727593lyra.horse

x86CSS is a working CSS-only x86 CPU/emulator/computer. No JavaScript required!

x86CSS is a working CSS-only x86 CPU/emulator/computer. Yes, the Cascading Style Sheets CSS. No JavaScript required.

What you're seeing above is a C program that was compiled using GCC into native 8086 machine code being executed fully within CSS.

GitHubFedi, Bluesky, Twitter

Frequently Asked Questions

Is CSS a programming language?

Do you really need to ask at this point?

How??

I plan on writing a blog post that explains how this works as well as many of the tricks used. Bookmark my blog or add it to your RSS reader.

Surely you still need a little bit of JavaScript?

Nope, this is CSS-only!

There is a script tag on this site, which is there to provide a clock to the CSS - but this is only there to make the entire thing a bit faster and more stable. The CSS also has a JS-less clock implementation, so if you disable scripts on this site, it will still run. JavaScript is not required.

My CSS clock uses an animation combined with style container queries, which means you don't need to interact with anything for the program to run, but it also means its a bit slower and less stable as a result. A hover-based clock, such as the one in Jane Ori's CPU Hack, is fast and stable, but requires you to hold your mouse on the screen, which some people claim does not count as turing complete for whatever reason, so I wanted this demo to be fully functional with zero user input.

But you still need HTML, right?

Not really... well, kind of?

This entire CPU runs in just CSS and doesn't require any HTML code, but there is no way to load the CSS without a <style> tag, so that much is required. In Firefox it is possible to load CSS with no HTML, but atm this demo only works in Chromium-based browsers.

What preprocessor do you use?

I straight up just write CSS! The CSS in base_template.html is handwritten in Sublime Text, but for the more repetitive parts of the code I wrote a python script.

Is this practical?

Not really, you can get way better performance by writing code in CSS directly rather than emulating an entire archaic CPU architecture.

It is fun though, and computers are made for art and fun!

Can I write/run my own programs?

Yes, but you'll have to compile them yourself. See below.

What's x86?

x86 is the CPU architecture most computers these days run on. Heavily simplified, this demo runs the same machine code in CSS that your computer does in its processor. To be fair, modern x86 is 64bit and has a bunch of useful extensions, so it's not quite the same - this here is the original 16bit x86 that ran on the 8086.

What's horsle?

neigh.

Compatibility

This project implements most of the x86 architecture, but not literally every single instruction and quirk, because a lot of it is unnecessary and not worth adding.

The way I approached this project was by writing programs I wanted to run in C, compiling them in GCC with various levels of optimization, and then implementing every instruction I needed. This way I know I have everything I need implemented.

There is some behaviour that's wrong, and some things are missing (e.g. setting the CF/OF flag bits). That's okay.

- 0 1 2 3 4 5 6 7 8 9 A B C D E F
0ADDADDADDADDADDADDPUSHPOPORORORORORORPUSH--
1ADCADCADCADCADCADCPUSHPOPSBBSBBSBBSBBSBBSBBPUSHPOP
2ANDANDANDANDANDANDES:DAASUBSUBSUBSUBSUBSUBCS:DAS
3XORXORXORXORXORXORSS:AAACMPCMPCMPCMPCMPCMPDS:AAS
4INCINCINCINCINCINCINCINCDECDECDECDECDECDECDECDEC
5PUSHPUSHPUSHPUSHPUSHPUSHPUSHPUSHPOPPOPPOPPOPPOPPOPPOPPOP
6--------------------------------
7JOJNOJBJNBJZJNZJBEJAJSJNSJPEJPOJLJGEJLEJG
8GRP1GRP1GRP1GRP1TESTTESTXCHGXCHGMOVMOVMOVMOVMOVLEAMOVPOP
9NOPXCHGXCHGXCHGXCHGXCHGXCHGXCHGCBWCWDCALLWAITPUSHFPOPFSAHFLAHF
AMOVMOVMOVMOVMOVSBMOVSWCMPSBCMPSWTESTTESTSTOSBSTOSWLODSBLODSWSCASBSCASW
BMOVMOVMOVMOVMOVMOVMOVMOVMOVMOVMOVMOVMOVMOVMOVMOV
C----RETRETLESLDSMOVMOV----RETFRETFINTINTINTOIRET
DGRP2GRP2GRP2GRP2AAMAAD--XLAT----------------
ELOOPNZLOOPZLOOPJCXZININOUTOUTCALLJMPJMPJMPININOUTOUT
FLOCK--REPNZREPZHLTCMCGRP3aGRP3bCLCSTCCLISTICLDSTDGRP4GRP5
GADDORADCSBBANDSUBXORCMPROLRORRCLRCRSHLSHR--SAR
GTEST--NOTNEGMULIMULDIVIDIVTEST--NOTNEGMULIMULDIVIDIV
GINCDEC------------INCDECCALLCALLJMPJMPPUSH--

Compiling

You can run your own software in this emulator!

If you have 8086 assembly ready to go, clone my repo, and put the assembly in a file called program.bin. Then, put the path to the _start() function in program.start as a number. Once that's set, you can just run build_css.py with Python3 (no dependencies required!) and the output will be in x86css.html.

If you want to write C code, you can do so using gcc-ia16 (you can build it yourself or install it from the PPA). The build_c.py script does the build with the correct flags and also makes the program.bin/start files. Don't forget to run build_css.py after! This building setup works on both Linux and WSL1/2 (I haven't tried on macOS).

By default there is 0x600 bytes (1.5kB) of memory, but this can be increased in the build_css.py file as necessary. The program gets loaded at memory address 0x100. There's some custom I/O addresses for you to be able to interface with the program.

Here's an example program:

static const char STR_4BYTES[] = "hell";
static const char STR_8BYTES[] = "o world!";

void (*writeChar1)(char);
void (*writeChar4)(const char[4]);
void (*writeChar8)(const char[8]);
char (*readInput)(void);

int _start(void) {
  // Set up custom stuff
  writeChar1 = (void*)(0x2000);
  writeChar4 = (void*)(0x2002);
  writeChar8 = (void*)(0x2004);
  readInput = (void*)(0x2006);
  int* SHOW_KEYBOARD = (int*)(0x2100);

  // Write a single byte to screen
  writeChar1(0x0a);
  // Write 4 bytes from pointer to screen
  writeChar4(STR_4BYTES);
  // Write 8 bytes from pointer to screen
  writeChar8(STR_8BYTES);
  // Write a character from custom charset
  writeChar1(0x80);

  while (1) {
    // Show numeric keyboard
    *SHOW_KEYBOARD = 1;
    // Read keyboard input
    char input = readInput();
    if (!input) continue;
    *SHOW_KEYBOARD = 0;
    // Echo input
    writeChar1(input);
    break;
  }

  while (1) {
    // Show alphanumeric keyboard
    *SHOW_KEYBOARD = 2;
    char input = readInput();
    if (!input) continue;
    *SHOW_KEYBOARD = 0;
    writeChar1(input);
    break;
  }

  return 1337;
}

Credits

Greetz/thanks to:

Your browser is unable to run this demo. Please try with an up-to-date Chromium-based browser.


Read the original article

Comments

  • By Dylan16807 2026-02-246:201 reply

    > A hover-based clock, such as the one in Jane Ori's CPU Hack, is fast and stable, but requires you to hold your mouse on the screen, which some people claim does not count as turing complete for whatever reason, so I wanted this demo to be fully functional with zero user input.

    That hover clock post is from 2023 and the "some people claim does not count" post is 2022. They were probably talking about the ones that make you check thousands of boxes to drive the logic forward.

    Anyway, very cool advancement.

    • By rebane2001 2026-02-248:231 reply

      I wasn't sure whether to address the disconnect in the FAQ - I wanted it to be short and readable.

      The idea is that, since a long time ago, there has always been demos that prove turing completeness and other programmy qualities in CSS, but that which people dismiss as requiring user inputs. The ones around by the time the comment got made were definitely at the "keep on clicking on the same spot on the screen" level - essentially just providing a clock.

      And seeing discussion from after Jane Ori's hack, many still claim that even as much as hovering your mouse on a specific part of the screen makes css not a programming language.

      • By Dylan16807 2026-02-2417:491 reply

        > essentially just providing a clock

        "providing a clock" is not something to dismiss though. Arithmetic plus looping will give you a Turing machine, so you do need both or you're just showing the ability to do arithmetic.

        And a proper Turing machine doesn't need an extra line of template html for each iteration. It's much easier to forgive finite memory, since a small amount of memory can go for billions of years while an iteration limit runs out fast.

        This one passes all the bars, but I do think the bars were overall legitimate.

        > many still claim that even as much as hovering your mouse on a specific part of the screen makes css not a programming language.

        That bar is pretty silly.

        • By rebane2001 2026-02-2418:201 reply

          clock != looping, those examples already loop (dont need a line per iteration), but just dont have a built-in clock

          and requiring a clock is imo dismissable, because pretty much all modern technology needs a clock too (either from the power grid, or from a hardware component designed for it)

          • By Dylan16807 2026-02-2419:18

            Sure, we can separate loops from clocking for the most part. But it doesn't really change the analysis. These loop. The stuff from several years ago didn't loop properly.

            As a tangent though, the system is already powered, you shouldn't need a secondary power source to make your Turing machine go. Something there still feels incomplete, like it probably passes but with an asterisk. But that distinction doesn't matter for CSS since it can self-clock.

  • By mbreese 2026-02-2412:362 reply

    This is a cool demo, but it tells me that CSS might be too complex now. Why should you be able to emulate a CPU with a styling language? I’m not sure what you get by using a Turing complete language for visual styling.

    • By FartyMcFarter 2026-02-2412:502 reply

      I don't know much about CSS, but Turing completeness is notorious for showing up in systems unintentionally.

      It doesn't take much to be Turing-complete - if a system provides unbounded read/write memory plus branching or conditional recursion you're usually there.

      As an example, Magic The Gathering (the card game) is Turing-complete: https://arxiv.org/abs/1904.09828 . You can use creature tokens as memory and various game mechanics to do flow control. Was this intentional by the designers? Most likely not...

      • By mycall 2026-02-2413:21

        * MOV x86: using memory mapped lookup tables, you can simulate logic gates and branching using only MOV.

        * PowerPoint (Without Macros): using On-Click Animations and Hyperlinks, shapes on slides act as the tape and clicking them triggers animations that move the head or change the state of the slide.

        * find and mkdir (Linux Commands): find has a -execdir flag executes commands for directories it finds. By using mkdir to create specific folder structures, you can create a feedback loop that functions as a Tag System (aka universal computation).

        * Soldier Crabs: Researchers showed that swarms of Mictyris guinotae can be funneled through gates to implement Boolean logic. While a full computer hasn't been built with them, the logic gates (AND, OR, NOT) are the building blocks for one.

        Even water is Turing Complete:

        * Fluidic Logic Gates: the Coandă effect is the tendency of a fluid jet to stay attached to a convex surface. By using tiny air or water jets to push a main stream from one channel to another, you can create the fluid equivalent of a transistor.

        * MONIAC (Monetary National Income Analogue Computer)

        * Navier-Stokes equations describe how fluids move are TC.

        * In 2015, Stanford researchers developed a computer that operates using the physics of moving water droplets. Tiny iron-infused water droplets moved by magnetic fields through a maze of tracks. The presence or absence of a droplet represents a 1 or a 0. By colliding or diverting each other, the droplets interact perform calculations.

      • By lproven 2026-02-2413:131 reply

        > Turing completeness is notorious for showing up in systems unintentionally

        Greenspun's 10th law.

        https://wiki.c2.com/?GreenspunsTenthRuleOfProgramming

        • By Dylan16807 2026-02-2419:291 reply

          That's on the opposite end of the complexity spectrum.

          • By lproven 2026-02-2515:321 reply

            Perhaps so, but ISTM that it encapsulates the same basic point. Try to make something rich and general and you often end up re-implementing a whole computer inside your computer.

            Which is why these days it's easier in many cases to just embed an Arm core and implement your controller's functionality in software.

            • By Dylan16807 2026-02-2518:50

              > Try to make something rich and general

              It's also easy to stumble into Turing completeness when you're just trying to let one setting modify another setting in a basic way.

    • By namuol 2026-02-2417:30

      Less JavaScript is a bad thing now?

  • By dmitrygr 2026-02-243:542 reply

    There is absolutely no reason for css to be turing complete. None. That being said, well done

    • By notepad0x90 2026-02-245:302 reply

      Can an argument be bade that CSS only exists becuase javascript failed to develop a styling component to displace it?

      I like to think webassembly is the right track. But ECMAScript and CSS alike need(ed) to devolve into a simpler byte-code like intermediary language syntax.

      Browsers supporting complex languages has always been a bad idea, what they need to support is capabilities, and access and security primitives. wasm hasn't displaced javascript, because afaik, the wasm spec for browsers doesn't require them to implement javascript (and ideally, CSS) via wasm.

      Instead of distilling, simplifying and speccing CSS and Javascript, browsers caked on layers upon layers of complicated features. The idea that browsers should define and regulate the languages developers use to write front-end code needs to die.

      • By Leszek 2026-02-246:111 reply

        The complex parts of JavaScript are the semantics, not the syntax. You could reasonably easily spec a bytecode for JS to get rid of the syntax part, but nothing would change in the complexity (almost all modern engines parse to bytecode as the first step and operate on bytecode from then on).

        If you wanted to implement JS in wasm, you'd either need a bunch of wasm extensions for JS semantics (dynamic object shape, prototypal inheritance, etc), or you'd need to implement them in wasm from scratch and basically ship a JS runtime written in wasm. Either that, or you need to change the language, which means de facto adding a new language since the old JS still has to stick around for old pages.

        • By notepad0x90 2026-02-2418:561 reply

          I admit I don't have depth of knowledge with how JS and Wasm work behind the scenes, but you can already compile JS to wasm. Forgive the ignorance, but what am I missing here? When you compile JS to wasm, aren't the semantics and language behaviors already addressed?

          My understanding was that things like DOM manipulation APIs would need to be (and are) exposed to the WASM runtime so that the JS-to-WASM compiler could do all the right bytecode "linking".

          My idea is that if JS itself was require to in WASM, and native JS support didn't exist, the complexity in browsers would be reduced dramatically. And developers will be free to use different languages. Browsers will focus on APIs instead of language semantics. no more V8!

          • By Leszek 2026-02-2421:071 reply

            You can't currently "compile JS to Wasm", you can compile a JS engine (likely written in C++) to Wasm and run JS inside that (almost certainly at lower speed). So instead of "no more V8", it's closer to "ship V8 as part of the website, instead of part of the browser". Exposing DOM APIs to Wasm is a completely different question, though even those would actually have to be new APIs with a new API design that works for Wasm, since the existing DOM APIs are effectively defined in terms of JS objects being passed around.

            • By notepad0x90 2026-02-2422:21

              I didn't know this, thanks for explaining. If DOM APIs could be reworked for wasm support, and JS can work with those APIs, that would suffice. It won't get sites off normal JS any time soon. As far as speed, is it an optimization problem? I'd think just running bytecode is faster than interpreting a script. It doesn't need to be backwards compatible with existing JS code, it just needs to be possible to write front end code in JS for WASM. Migrating to the new approach could be similar to migrating away from Flash, ActiveX and Applets, but at a much larger scale.

              10-20 years from now, is it really ok to be stuck with JS and CSS? The complexity is compounding, ever newer webapi's get encoded as JS APIs, and the dependency mess will only increase. Browsers are about as complex in some ways as an operating system. If we think about the world in terms of decades, what is the plan? This is a very serious issue, because of how much the world depends on browser tech, and effectively, Google is the arbiter and overseer of that technology. I don't think this will work out well, unless there is some timely foresight in the architecture.

      • By nsonha 2026-02-246:341 reply

        > CSS only exists becuase javascript failed to develop a styling component to displace it

        there is no sortage of projects that do it (especially during the react era, people wanted to get rid of both html and css) but they get pushed down by dogma/inertia mostly. There was iOS constraint layout language ported to js. Seemed pretty cool, but the guy behind it decided to give up and everyone was like welp we tried, didn't work.

        • By notepad0x90 2026-02-2418:571 reply

          It sounds like the consistent pattern is the requirement for browsers to support it. If browsers supported intermediary languages instead, that might be ideal?

    • By naillang 2026-02-2412:161 reply

      [dead]

      • By Dylan16807 2026-02-2419:55

        Well, redstone was designed to be able to do logic from the start. The first version had wires, a couple input options, a couple output options, and NOR gates, already updating on a global clock. The ability to make computation circuits was clear.

HackerNews