Getting started
Install
Section titled “Install”npm install @lo-tek/interfacesThe package has no dependencies. The web components are ordinary HTMLElement classes, and nothing is registered by import alone, so a page that only uses the CSS components takes no JavaScript from the package. The Tailwind theme is plain CSS, so the package requires nothing from Tailwind.
Import the stylesheets
Section titled “Import the stylesheets”tokens.css defines all the CSS variables, and every other stylesheet builds on it. It must be loaded, and comes first in the examples out of habit: the order between it and the others does not matter.
One stylesheet per component, with tokens.css first:
// main.js or main.ts, at the topimport "@lo-tek/interfaces/tokens.css"import "@lo-tek/interfaces/button.css"Or everything in one import, without choosing:
import "@lo-tek/interfaces/lotek.css"lotek.css contains tokens.css and all the component stylesheets, so it replaces the whole list. The Tailwind theme is not included, and does not belong in a JavaScript import at all: tailwind.css is a @theme block Tailwind’s own compiler must read, and is set up as an @import in your own CSS. See Tailwind.
One file with everything, one round trip:
<head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/dist/lotek.css" /></head>Or just what you use:
<head> <link rel="stylesheet" href="/node_modules/@lo-tek/interfaces/src/tokens/tokens.css" /> <link rel="stylesheet" href="/node_modules/@lo-tek/interfaces/src/components/css/button/button.css" /></head>Without a build step, some of the stylesheets fetch their parts with @import, which the browser only sees once the file has loaded. dist/lotek.css has everything flattened, without @import, and is therefore the fastest choice when you link. From a CDN, write the real path in the package, that is @lo-tek/interfaces@0.36.0/src/tokens/tokens.css. Short names like @lo-tek/interfaces/tokens.css are entries in exports, and only a build step reads those.
If you link without a build step, use lotek.css. One round trip of 103 kB, 13 kB compressed with gzip, and no @import the browser has to load the file to discover. The list with one stylesheet per component is for those with a build step: there, a page with only buttons does not load CSS for a dialog. The “Plain HTML” tab on each component page lists the links to the individual stylesheets, since the list also says what the component requires, and lotek.css covers each of them.
Add the first component
Section titled “Add the first component”With tokens.css and button.css imported:
<button class="lt-button">Submit application</button><button class="lt-button" data-variant="secondary">Save draft</button>No registration, no start-up code. The CSS components are just classes.
Register the web components
Section titled “Register the web components”Frame and standalone components are web components, and must be registered once when the app starts. defineLt() registers all nine:
// main.ts, runs once, on import and not in an effectimport "@lo-tek/interfaces/tokens.css"import "@lo-tek/interfaces/field.css"import { defineLt } from "@lo-tek/interfaces/register"
defineLt()Without build tools, point at the built file:
<script type="module"> import { defineLt } from "https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/dist/register.js" defineLt()</script>The call should happen as early as possible, before anything renders and never in a useEffect. An element that is in the page before registration is a plain HTMLElement with no behaviour, and a method call on it in that gap fails. The helpers showToast, reportFailure, reportSuccess, extendSession and resetSession instead wait for the registration and make the call when it arrives. The registration pulls in the code for all nine. If you use one of them, call its own function instead, like defineLtField() from @lo-tek/interfaces/field.
Calling them more than once is harmless, because the functions check themselves whether the element is registered. They also do nothing on a server, where there is no customElements to register in, so they can sit in a file that runs in both places.
<lt-field> <label class="lt-label" for="name" data-required="symbol">Full name</label> <input class="lt-input" id="name" type="text" name="name" required /> <p class="lt-help-text">As it appears in your passport.</p></lt-field>If the label, the field and the help text are there, but the help text is not connected to the field with aria-describedby, the define function has not run. The markup in a frame component is yours and is shown regardless, with two exceptions: Tabs shows only the first panel, and Suggestion hides the list, until they are registered, since they would otherwise jump when the script arrives. If the script fails, the user cannot reach the other tabs. Only the two standalone ones, <lt-toast> and <lt-connection-status>, are empty until they are registered. An <lt-session-timeout> shows nothing until the warning is due, and without registration it never comes.
Use the entry points
Section titled “Use the entry points”Stylesheets
Each component has its own stylesheet, named after its page: @lo-tek/interfaces/button.css, /checkbox.css, /table.css and so on. Import the ones you use.
| Import | Contents |
|---|---|
@lo-tek/interfaces/lotek.css | Everything in one file: tokens and every component, without @import. Not the Tailwind theme |
@lo-tek/interfaces/tokens.css | All CSS variables. Required by everything else. |
@lo-tek/interfaces/<component>.css | One component, with the same name as its page |
@lo-tek/interfaces/sr-only.css | .lt-sr-only, text only screen readers get |
@lo-tek/interfaces/tailwind.css | LO-TEK’s values as a Tailwind theme. Optional, see Tailwind. |
Some stylesheets bundle what they need, so you do not have to import the parts separately: field.css, error-summary.css, fieldset.css, search.css, session-timeout.css and suggestion.css.
All the web components need a stylesheet. None of them uses shadow DOM, so the elements are ordinary elements with the system’s classes, and the classes must be loaded on the page.
JavaScript and TypeScript
The whole component API is in one namespace:
import { lt } from "@lo-tek/interfaces"
lt.button({ variant: "secondary" })lt.input({ type: "email", state: "invalid" })lt.badge({ color: "success" })Type lt. in the editor to see what there is. If you use React, import from @lo-tek/interfaces/react. The calls are the same, but the return value uses className and htmlFor. See Type-safe use.
| Import | Contents |
|---|---|
@lo-tek/interfaces | lt with all the components, all the types, and the web component registrations |
@lo-tek/interfaces/react | The same lt, but with className and htmlFor |
@lo-tek/interfaces/react-jsx | Types for web components in JSX |
@lo-tek/interfaces/dom | setAttributes: applies an attribute set to an element without JSX |
@lo-tek/interfaces/register | defineLt: registers all the web components in one call |
@lo-tek/interfaces/diagnostics | diagnoseMarkup: checks HTML against the elements and classes, as the editor does. See The editor |
@lo-tek/interfaces/field | defineLtField, the class LtField |
@lo-tek/interfaces/session-timeout | defineLtSessionTimeout, the class LtSessionTimeout, extendSession and resetSession |
@lo-tek/interfaces/connection-status | defineLtConnectionStatus, the class LtConnectionStatus, reportFailure and reportSuccess |
@lo-tek/interfaces/tabs | defineLtTabs, the class LtTabs |
@lo-tek/interfaces/error-summary | defineLtErrorSummary, the class LtErrorSummary |
@lo-tek/interfaces/popover | defineLtPopover, the class LtPopover |
@lo-tek/interfaces/toast | defineLtToast, the class LtToast, showToast |
@lo-tek/interfaces/suggestion | defineLtSuggestion, the class LtSuggestion |
@lo-tek/interfaces/dialog | defineLtDialog, the class LtDialog |
@lo-tek/interfaces/field-core | computeFieldAttributes: what lt.field and <lt-field> share |
@lo-tek/interfaces/host-element | HostElement and defineElement: the base the web components inherit from |
@lo-tek/interfaces/tokens | cssTokens, colorTokens, darkColorTokens, darkTokens, the types CssToken, ColorToken, Breakpoint and Container, Breakpoints, Containers, and what the colour families, roles and layers are for |
Each CSS component also exists as its own entry point (@lo-tek/interfaces/button, /badge, /link and so on) with a function named the same as the component. The exception is /switch, where the function is called switchControl, since switch is a reserved word. The builder functions for the web components, like lt.tabs() and lt.dialog(), have no entry point of their own: they are in lt and in the main entry point. /tabs, /dialog and the others have the class and the define function.
lt is optional. class="lt-button" data-variant="secondary" by hand gives the same result; the functions exist to catch typos.
Understand versioning
Section titled “Understand versioning”The package follows semantic versioning. The public API is larger than the functions: the class names, the attributes the builder functions write (data-* and aria-* included), the token names, the --lt-* variables and the entries in exports are all things you can build on.
Before 1.0, a new minor version, such as a move from 0.x to 0.(x+1), can have breaking changes. They are then listed under the heading “Breaking” in the changelog, with what you need to change. From 1.0, none of these change without a new major version.
npm install writes the version with ^ in front. Below 1.0, ^ only accepts new patches within the same minor version, so you get a new minor version only when you ask for it, by writing the new number in package.json or installing the package again.
The package is pure ESM, with no CommonJS build. Use import.
What has changed in each version is in CHANGELOG.md, which also ships with the package.
Go further
Section titled “Go further”- Frameworks: concrete setup for plain HTML, React, Astro and Datastar
- The editor: completion and error messages in VS Code and JetBrains, also without TypeScript
- Design tokens: which variables exist, and what they are called
- Tailwind: setup and LO-TEK’s values as utility classes
- Button: the simplest component to start with
- Patterns: whole forms, lists and pages put together