Slack
- Vendored Electron
- ~300MB
- Slack itself
- ~11MB
- Licence files
- ~15MB
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)
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
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 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.
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
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
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.
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
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
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
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
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
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
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();
}
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
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.
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
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.
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.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)).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.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..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.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
#include "rayclay.h"
int main(void) { return rcRunApp(NULL); }
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.