Skip to content

How it works

Every component page follows the same structure: classes, API reference, examples, accessibility notes, and dos and don’ts.

The Classes table shows the classes you add to your markup. For JavaScript components, generated markup is shown separately so you can see what the component creates and customize it with CSS.

Examples show the markup alongside the rendered component. If JavaScript is required, the example source is split into HTML, TS, Angular, and React.

The badge next to each component tells you whether it needs JavaScript. CSS components only require the class shown in the documentation. JS components are custom elements and require the kit’s JavaScript.

Most components are CSS-only: accordion, badge, banner, button, card, grid, input, link, modal, progress, sidebar, spin, table, toast, and tooltip.

The JavaScript components are tabs, pagination, dropdown, carousel, gauge, and scrollbar.

The distinction comes down to behavior CSS cannot provide. A modal can use the browser’s native <dialog> element, so it only needs styling. Tabs need JavaScript to manage selection state, ARIA attributes, and keyboard navigation.

Generated markup is still normal DOM. You can query it, style it, and interact with it like any other element. The k- prefix keeps the kit’s classes separate from your application’s classes.

The kit defines its cascade layers in this order:

@layer theme, base, components, utilities;

Later layers take precedence. Your unlayered CSS takes precedence over all of them.

This is the intended customization model. Override the component styles you need without fighting specificity.

The kit does not include a reset, so adding the stylesheet does not change unrelated elements. Kit selectors also include the expected HTML element, such as button.k-btn or span.k-badge, to prevent a k- class from styling the wrong element.

Components use semantic color tokens rather than raw palette values.

For example:

--k-primary
--k-surface
--k-fg
--k-border
--k-accent

These tokens point to the underlying palette and can change between themes. Components use the semantic token, so changing the token changes every component that uses it.

Tailwind utilities use the same tokens. For example, bg-k-primary resolves through --k-primary, which means changing the theme updates the page without rebuilding the stylesheet.

Load your theme after the kit stylesheet and override the tokens you want to change.

import 'k-web-ui';
import './theme.css';
:root {
--k-primary: #1d4ed8;
--k-primary-hover: #3b82f6;
--k-border: #3b82f6;
--k-ring: #3b82f6;
}
[data-theme='k-light'] {
--k-surface: #eff6ff;
--k-fg-muted: #1e40af;
}
[data-theme='k-dark'] {
--k-surface: #020617;
--k-fg-muted: #93c5fd;
}

You only need to define the tokens you want to change. Everything else keeps the kit’s default value.

You can also create your own theme name:

<html data-theme="night">
[data-theme='night'] {
color-scheme: dark;
--k-surface: #020617;
--k-surface-raised: #0f172a;
--k-surface-hard: #1e293b;
--k-surface-soft: #020617;
--k-fg: #f8fafc;
--k-fg-muted: #93c5fd;
--k-border-hard: #38bdf8;
--k-accent: #f8fafc;
--k-accent-fg: #020617;
--k-accent-hover: #e0f2fe;
}

If a token is not overridden, it inherits its existing value. Make sure to override every token that should change between themes.

Switch themes by changing the data-theme attribute:

document.documentElement.setAttribute('data-theme', 'night');

The Colors page lists the available tokens. Styles covers radius, shadows, and focus rings.

Content stays in your HTML. A JavaScript component finds it by id: give the host an id, then number the content nodes from zero.

<k-tabs id="sections" class="k-tabs" aria-label="Sections"></k-tabs>
<div id="sections-0">The first panel.</div>
<div id="sections-1">
<k-gauge id="upload" class="k-gauge"></k-gauge>
</div>

The element never moves, clones, or rewrites those nodes. It adds a class, the ARIA it needs, and hidden plus inert on the ones that are closed. That means panel content can be anything — another component, a table, a form — and it can be rendered by Angular, React, Vue, or a template on your server. Nodes that show up after the element connects are picked up when they land.

Tabs, pagination, and carousel work this way. Pagination and carousel take no options at all, since counting the nodes is the whole configuration.

Anything that isn’t content is a property, set in JavaScript:

document.getElementById('sections').options = [
{ label: 'Overview' },
{ label: 'Usage' },
];

Attributes are not part of the API. Visual variants are modifier classes you write on the tag (k-tabs--lg, k-carousel--autoscroll, k-scrollbar--no-autohide), and a class change patches in place rather than rebuilding.

Interactive components use a single k-change event. Gauge is the exception because it displays a value without producing a user-driven change.

document.querySelector('k-tabs').addEventListener('k-change', (event) => {
console.log(event.detail.selected);
});

The event detail depends on the component:

  • Tabs, pagination, and carousel: { index }, zero-based like the ids
  • Dropdown: { index, label, href }
  • Scrollbar: { scrollTop, scrollLeft }

The event fires when a component changes through user interaction or a method call. Tabs, pagination, and carousel all move with select(index), so one listener and one call cover the three of them.