Your note-taking app should not be 300MB.

RayClay is a pure-C GUI library with a flexbox layout engine and Tailwind-style tokens. Your users see the same UI. Your download drops from about 300MB to about 5MB, because you stop shipping them a copy of Chromium. One source builds to Windows, macOS, Linux - and the browser.

Build a window in two lines See the numbers Source on GitHub (opens in a new tab)

What you actually download Four sizes, one linear scale, 0 to about 300MB.
Chromium engine, alone The browser engine on its own, before a line of your app.
~100MB
Electron, empty window The floor. A window that does nothing yet.
~150MB
Electron as Slack ships it Vendored into the file you actually download.
~300MB
RayClay Layout engine, renderer and your app, plus whatever runtime the platform makes it carry.
~5MB

Every figure is an estimate. The first three are three different things measured three different ways - an engine, a floor, and a shipped bundle - which is why they sit on the ladder separately instead of being averaged into one number you could argue with. The third one you can check on your own disk in about a minute. The fourth is an estimate too, and it now has measurements beside it: a notepad application, built eight ways for the comparison page, downloads as 3.2MiB on Linux, 602.1KiB on macOS and 11.1MiB on Windows, because what a platform makes you carry differs. Measured figures, eight frameworks across three machines, are on the comparison page.

The problem

Every app on the new machine was about half a gigabyte.

This project started with a new laptop and an afternoon of setting it up: the handful of applications you install before you can do any work at all. They came in at about half a gigabyte each. Not one of them. All of them.

That is the part worth sitting with. On its own, a large application is a story about that application: it does a lot, there is a lot in it, fine. The same number turning up for a chat window, a notes app and an editor is not a story about any of those products. It is a story about a shared floor. Half a gigabyte is not what a sidebar and a list cost. It is what a browser costs, paid once per application, by every person who installs one.

Think of the fifteen apps you actually use. About three quarters of them are JavaScript. Not in the browser, where JavaScript belongs - on your desktop, where most of them quietly brought their own copy of a web browser along to draw a sidebar and a list.

Nobody decided this. Every one of those calls was defensible in the room where it was made, and their sum is a category where about half a gigabyte per application raises no eyebrows.

The evidence

Slack is about 11MB of Slack.

Slack ships roughly 300MB of vendored Electron, about 11MB of actual Slack source, and about 15MB of licence files. Obsidian ships about the same 300MB of Electron wrapped around about 8MB of Obsidian. Over 90% of what you downloaded is framework - and it is the same framework, give or take a version, in both.

A mouse and an elephant share about 90% of their DNA. So do Slack and Obsidian.

Slack

Vendored Electron
~300MB
Slack itself
~11MB
Licence files
~15MB

Obsidian

Vendored Electron
~300MB
Obsidian itself
~8MB

The top line is the same framework in both. It is the largest thing in each app and the least of what either app is.

The obvious question

So why don't they just share it?

Because the two apps pin different Electron versions, and a shared copy would break one of them. Self-containment is the main reason Electron beat Tauri: a Tauri app downloads far less, and on macOS less than RayClay: about 285KiB of application against Electron's roughly 114MiB, on the same machine and the same app. That gap is an accounting one as much as an engineering one: WebKit ships with macOS, so the engine a Tauri app renders in is not charged against its download there. Where the engine has to be counted, the same app is about 23MiB on Windows and about 263MiB on Linux - and on Linux that is more than Electron on the same box. Ten Electron apps later, you are carrying perhaps three to five gigabytes of duplicated framework you can never deduplicate.

And the asymmetry is the whole of it. The trade is priced once, per application, in a room full of people building that application. It is paid many times, per machine, by someone who was not in the room - and being unable to reclaim it is precisely the property those teams were buying.

RayClay keeps the self-containment. It just makes the thing you are containing about 5MB.

The cost

Every layer between your code and the screen is charged to someone's machine.

Follow one click from your source code to a lit pixel. Everything it passes through on the way is a layer, and every layer is code that has to be downloaded, stored, held in memory and executed on hardware somebody else paid for.

What a click passes through Five layers in a web-stack desktop app, from your code down to the glass. RayClay removes the middle three.
  1. Your application

    About 11MB in Slack's case, about 8MB in Obsidian's. The only layer in this list that anybody installed the app for.

    Kept

  2. The framework, as it is actually vendored

    Not the floor: what ends up inside the file your users download, which is roughly 300MB in both of the applications broken down above.

    Removed

  3. A JavaScript runtime, and a shell to package it

    An empty Electron window - no features, nothing on the screen but a frame - sits at a floor of around 150MB. That is the price of admission, charged before you have written anything at all.

    Removed

  4. A browser engine

    Parses HTML, resolves a cascade, maintains a DOM and rasterises the result. Roughly 100MB on its own. Worth noticing on the way past: it is a C++ program. Nobody picked JavaScript for the layer where performance was load-bearing.

    Removed

  5. The window and the GPU surface

    The operating system already has both, and every toolkit on every platform is built on them. They cost your download nothing. This layer was never the problem.

    Kept

The three marked Removed are not three downloads to add up. They are three views of one thing, measured three different ways: the engine inside the runtime inside the file that ships. Roughly 300MB of it is the framework, wrapped around about 11MB of Slack. Every figure here is an estimate; the measured ones, eight frameworks across three machines, are on the comparison page.

A UI library cannot remove the first layer and must not remove the last. Everything between them exists for one purpose: to give your code a DOM to talk to. Take the DOM away and the middle three have nothing left to do.

That is the entire design. RayClay declares a layout in C, lays it out with a flexbox engine, and draws it to the surface the operating system already handed you. There is no HTML to parse, so there is no parser. No cascade to resolve, so there is no style engine. No script to run, so there is no runtime. Those layers are not optimised. They are absent - and the person using your app loses no feature by their absence, because they were never features. They were plumbing.

And the honest half: removing the DOM removes what the DOM was giving you. No cascade, no selectors, no specificity, no hover: variants, no grid, no flex-wrap, no margin field - and no accessibility tree, which is a heavier loss than all the rest put together and is the first item in the disclosure further down this page.

The objection

“Modern hardware is powerful enough.”

It is the argument every JavaScript desktop app leans on, and there is a version of it that is true: modern machines really do have more headroom than they did five years ago.

The rebuttal

Headroom is a budget for features, not for a slower list view.

The power of modern hardware should buy your user more of your app, not a laggier version of the same one. A MacBook Neo is modern hardware and still has 8GB of RAM - with Chrome open, a couple of tabs, one other app, and yours, there is no headroom left to spend on your basic UI. Keep the UI lean and the headroom is yours to spend on depth.

And when your app balloons to a gigabyte while a competitor ships the same features in 30MB, your users will feel it whether or not they can name it. If every app in your category is JavaScript, nobody has anything to compare you to. The moment one of you isn't, everybody does.

The offer

The API was written for you.

RayClay is pure C99 - one header, one link line - but the layout DSL is flexbox, the styling tokens are Tailwind-shaped, and a component is just a function. If you have written JSX, you can read this. And if AI is writing the code anyway, the difficulty of C was never the reason to pick JavaScript.

rcColumn(.id = "sidebar", .bg = RC_SLATE_800, .p = 12, .gap = 8) {
    rcTextL("Inbox", .size = 18, .color = RC_WHITE);
    if (rcButton("send", "Send", RC_BTN_PRIMARY))
        send_message();
}
That is C. Not a transpiler, not a DSL over a DSL - the C preprocessor with designated initialisers.

The vocabulary is deliberate. .p, .gap and .bg are named that because you already know them. .w and .h take "grow", "fit", "200px" and "50%" because those are the values you would have typed anyway. Naming conventions are not a language feature and were never the thing that cost 300MB. The runtime was.

Where it stops borrowing, it stops abruptly. RayClay is immediate mode: your layout function is UI = f(state), called whenever the UI needs to redraw, with the reconciler removed. There is no useState because you own the struct, and no re-render bookkeeping because there is nothing to reconcile. A hover state is if (rcIsHovered(id)); a breakpoint is if (width < 768). The translation for web developers takes that boundary row by row: JSX, props, useState and Tailwind classes, each mapped to the thing that replaces it.

The claim is not that C is nicer to write than JavaScript. It is that the part of JavaScript worth keeping was the conventions, and conventions are portable.

The upside

Everybody wins, which is the annoying part.

Users get machines that stop feeling like swamp water. Developers get headroom to build instead of to optimise. Apple, Microsoft and every laptop maker on earth get to stop being blamed for hardware that is fine. There is no side of this that loses.

The person running your application
Gets back the disk, the memory and the background processes. Ten applications open at once on a machine that stays responsive is not a luxury feature; it is what they thought they were buying when they paid for the hardware.
The team building it
Gets its headroom back as a budget for features instead of a budget for working around its own runtime. A meeting about squeezing five per cent out of a list view is a meeting about a layer you chose and could remove.
Whoever made the machine
Carries the reputation for someone else's decision. A laptop holding a dozen copies of a browser engine does not feel slow to its owner as "my applications are large". It feels slow as "this computer is slow".

An argument in which every side wins is usually an argument with a mistake in it, so here is where the money really goes. Nobody in the room loses because nobody in the room is paying. The disk, the memory and the battery belong to someone who was not consulted, and shipping 300MB instead of 5 has never once appeared on the balance sheet of the team that shipped it.

The disclosure

RayClay is 0.8. Here is what that costs you.

Pre-1.0, one developer and an LLM, three months, as a second job. That is not a footnote to bury - it is part of the argument. If the difficulty of C was the reason your team picked JavaScript, then a library written this way, in this much time, by this few people, is the evidence that the reason has expired.

The list below is drawn from RayClay's own documentation, which names these so that none of them surprises you in month three, rather than being assembled here to look candid. It is the six that will reach you soonest and not the whole of it: the library's own getting-started guide carries the full set, including that scrollbars are opt-in and vertical only. Read it as the price of everything above. You will meet most of them in your first week, so you may as well meet them here.

Six sharp edges, worst first

No keyboard traversal, and no accessibility surface at all.
Widgets are pointer- and touch-first. Tab and Shift-Tab do not move focus between them, there is no focus ring, and only text inputs are selectable - you cannot select and copy a static label. There is no assistive-technology or screen-reader surface. For a whole class of products that is a hard stop, and you should treat it as one rather than as a to-do.
Strings do not copy.
rcText and rcTextC keep the pointer you hand them, so a function-local char buf[32] dangles by the time the frame draws. The docs call it the single place where C bites a JavaScript developer. Three patterns are safe: rcTextL("literal"), rcTextC(state->name), and rcText(rcFormat(rcAppArena(app), "%d items", n)).
One window per process.
rcAppCreate returns NULL if an app is already live, so a second simultaneous window is not available; sequential windows do work. It is a real refactor rather than a check waiting to be lifted, which is why it refuses cleanly instead of half-working. If you need a second view, dock it in the same window: rcBeginSplitPane for a resizable split, or a non-modal rcBeginModalEx panel that leaves the app live behind it.
Tailwind-inspired, not Tailwind-compatible.
CSS-familiar names and values, and that is the boundary. No class syntax, no cascade, no selectors, no hover: variants, no responsive breakpoints, no grid, no flex-wrap, and margin is an element rather than a field. if (rcIsHovered(id)) is your :hover; if (width < 768) is your media query.
Transitions are visible but unsupported.
Your editor will offer you .transition on the element declaration. Under the default on-demand runner it freezes mid-curve, because a transition only advances on a frame that actually lays out and nothing asks for the next one. The slot pool is a fixed 200 for the whole application. If your product's identity is in its motion design, price that in before you start.
Text is ASCII and Latin-1.
A printable codepoint outside that window - a smart quote, a euro sign, anything CJK - draws a question mark, and nothing warns you. Typing is narrower than drawing: a text field takes printable ASCII only, so an accented character cannot be typed or pasted into one even though the same character would render fine as a label. Colours take hex, rgb(), rgba() and 20 names - no hsl(), and not the full CSS set.

So what you would be taking on this week is a pre-1.0 library whose API will still move under you, maintained by one developer and a language model as a second job, with no accessibility story and a deliberately short feature list. If that disqualifies it for your product, then it disqualifies it. This page would rather you worked that out here than in month three.

What you would be taking on the other way is a runtime you did not write, cannot roll back and ship to every person who installs your product: roughly 300MB of it, wrapped around your own 11. Both of those are real risks. Only one of them is charged to your users.

The proof

Two lines is a window.

#include "rayclay.h"

int main(void) { return rcRunApp(NULL); }
That is the whole program. It compiles, opens a window, runs the frame loop, and cleans up after itself. Everything else on this site is a variation on that line.

Build a window in two lines If you know React, start here

No application is truly free. It is taking up real estate on hardware your users paid thousands for. You do not have to use this library. Please stop shipping a browser.