LO-TEK
A framework-independent design system. The components are CSS classes and web components, things the browser already understands. Whether the team uses React, Astro, Datastar or plain HTML, it is the same system and the same API.
A design system tied to one framework has to be rewritten when the framework is replaced. The browser's own APIs are not replaced.
LO-TEK grade
- Reads on any screen.
- Survives any patch.
- Says when it’s broken.
- Package
@lo-tek/interfaces- Status
- Before 1.0. Breaking changes are in the changelog.
- Licence
- MIT
- Source
- github.com/MarkusAugust/lo-tek
The Halcyon board asked Trace what it runs on.
“Whatever sends the page. Whatever shows it. Nothing in between.”
“So it needs a framework?”
“It doesn’t need anything. Bring yours.”
Architecture
-
css
A class on a plain HTML element. No JavaScript is loaded, and variants are set with data attributes. A button costs one class.
<button class="lt-button">- Button
- Field
- Table
- Card
-
frame
You write the HTML. The web component fills in the wiring where it is missing, adds keyboard handling and focus, and leaves the elements in the plain DOM.
<lt-field>…</lt-field>- Field
- Tabs
- Dialog
- Suggestion
-
standalone
The web component creates all of its content itself, because there is nothing for the server to send. A server that is gone cannot report it.
<lt-connection-status>- Connection status
- Toast
The whole reasoning, and the answer to when you use the builder function rather than the web component, is in Markup and behaviour.
What you do not have to solve yourself
-
The editor knows the components
Write
data-variant="secundary"by hand and nothing happens: the button keeps the default style, and the browser says nothing. Write it through the function and you get an error in the editor before you can save.<button {...lt.button({ variant: "secondary", })} > Save draft </button>The function returns a plain object of attributes, so the same call works in JSX, in an Astro template and in plain JavaScript. If you write HTML without TypeScript, the VS Code extension does the same, and JetBrains gets completion from the package itself.
-
Your CSS beats ours
All our CSS sits in its own cascade layer. An ordinary rule in your own CSS then beats ours, however specific ours is, and without
!important. If you only want to change shape or size, there is a variable per component.:root { --lt-button-radius: 9999px; }The tests open a browser and check that your rule actually wins, that the variable comes through, and that a Tailwind utility beats the component's own value.
-
The colours meet the contrast requirement
Every colour pair in the system is computed and checked against the WCAG requirement, in both light and dark themes. If you give your own brand colours, their families are computed afresh with
buildTheme.const theme = buildTheme({ accent: "#0062ba", }) // theme.violations is empty // when the contrast holdsThe colours are computed in OKLCH, because lightness in HSL means something different for each hue. The brand colour gives the hue, while the role sets the lightness and how much of the chroma is kept. Every page here is run through axe in both themes.
-
The coding agent does not have to guess
An agent that does not know the design system invents
lt-modal, hard-codes the colours and writes its own CSS when the stylesheet is missing. LO-TEK has a rulebook for agents per framework, and it ships with the package. When the agent is done, the check says what is wrong, with file and line.npx @lo-tek/interfaces check form.html form.html:3:13: warning: The class "lt-modal" does not exist in LO-TEK.The whole documentation also exists as Markdown, made from the same pages you are reading now, and llms.txt shows the way. More about the setup under Coding agents.