KANTO DS v1.12.0
Customise

Theme

Accent

Font

Corners

Density

Text size

Reset to Kanto
Release

Releases

GitHub Copy link

The current release is v1.12.0, from 26 October 2026.

Current release

v1.12.0 26 October 2026
npm install kanto-ds

History

From the changelog, because GitHub could not be reached from this browser. Try GitHub again

Notes

kanto-mcp: Kanto's documentation as an MCP server, for AI assistants as they code. claude mcp add kanto -- npx -y kanto-mcp, or the same command in Cursor, Windsurf or VS Code. Its tools list the components, give one's page and its whole API — attributes, properties, methods, events, slots, CSS parts — search the docs, and give the guides and each framework's setup, at the version installed. Published beside create-kanto, at the library's version.

Notes

kt-side-panel takes size: small (308px, as before) for a record or a form, medium (480px), large (720px) for more than a form — a comparison, a configuration. Never wider than the screen.

Notes

kt-user-menu takes block: at the foot of a sidebar, the account is a row shaped as the navigation items above it — their radius, padding and hover, the name and the email, a chevron — instead of a pill; the avatar alone, centred, in a rail; its menu as wide as the row.

Notes

create-kanto — start a project on Kanto in one command: npm create kanto@latest.

Notes

kt-avatar-group — the people on something, as avatars overlapping in a row.

Notes

kt-command-palette — the palette a product opens on Cmd+K.

Notes

A file for the CDN. dist/cdn/kanto.min.js is every element in one minified module, Lit and the default icons included, about 85 kB gzipped. The package's unpkg and jsdelivr fields point at it, so a page with no build step needs one script tag: <script type="module" src="https://cdn.jsdelivr.net/npm/kanto-ds@1"></script>. The release checks load it with nothing left to resolve.

Notes

import 'kanto-ds' and import 'kanto-ds/vue' registered no element in a production build. The package told bundlers its entry points had no side effects, so Vite and webpack dropped them, and every element with them; a development server does not tree-shake, which is why it only showed once deployed. Both entries now declare their side effects, and every release builds a real application against the packed package before it ships.

Notes

kt-multi-select — several choices from a list, typed to narrow it.

Notes

The documentation site flags components added in the last two months as "New" in its navigation, read from this changelog's Added entries. The package itself is unchanged from 1.5.1.

Notes

1.5.0 was tagged but never reached npm: a browser test failed on CI and the publish stopped before uploading. 1.5.1 is the first release on npm with everything listed under 1.5.0.

Notes

kt-date-picker — a day or a period, in a field that opens a calendar.

Notes

kt-checkbox — a choice that applies when the form is submitted.

Notes

Every string the elements write can be translated. The README promised that every default string was a property. About twenty were not — validation messages, "Previous" and "Next", the headers of the table every chart renders, and accessible names like "Clear" and "Dismiss" that only a screen reader hears — and could not be changed at all. They now all come from one registry:

Notes

React 19 is now the first-class path: it renders the kt- tags directly, so it needs no wrapper and no extra package. The React 18 wrappers stay, and stop costing everyone else a dependency.

Notes

Editor support. The package ships a Custom Elements Manifest (kanto-ds/custom-elements.json) describing every element's attributes, properties, events, slots and CSS parts, and the editor data generated from it: web-types.json, which JetBrains IDEs pick up on their own, and VS Code's HTML and CSS custom data. Plain HTML gets completion and hover docs; the README has the one-line VS Code setting.

Notes

kt-split-button, a primary action with a menu of related ones hanging off a caret. A caret press never surfaces as a click on the host, so one click listener means the primary action and nothing else.

Notes

First release of Kanto, a design system for data-dense product interfaces: one implementation, built on standard custom elements, that runs wherever HTML does.

Release notes

v1.12.0

Added

  • kanto-mcp: Kanto's documentation as an MCP server, for AI assistants as they code. claude mcp add kanto -- npx -y kanto-mcp, or the same command in Cursor, Windsurf or VS Code. Its tools list the components, give one's page and its whole API — attributes, properties, methods, events, slots, CSS parts — search the docs, and give the guides and each framework's setup, at the version installed. Published beside create-kanto, at the library's version.
  • The documentation site is read by search engines: each page has its own address — /components/kt-button/ — its title, its description and its canonical link, written as HTML at build for crawlers that do not run a script, with sitemap.xml and robots.txt.

Changed

  • The site's addresses are paths, not #/…. An old link still lands on its page, the purchase's return included; links move without a reload; a template's running demo is under /live/<id>/, and an old link to one reaches it.
  • A component's page is titled by its tag, kt-button — Kanto.

Fixed

  • A link between two component pages, written to the markdown file beside it, led to a missing page on the site; it leads to the page.

v1.11.0

Added

  • kt-side-panel takes size: small (308px, as before) for a record or a form, medium (480px), large (720px) for more than a form — a comparison, a configuration. Never wider than the screen.
  • kt-chat-message takes continued: several messages in a row from one person read as one turn — the avatar and the name are not drawn again, the words line up with the first, the time in the gutter on hover.

Fixed

  • kt-side-panel's test of its slide-out waited a fixed time, and failed on a loaded machine; it waits for the panel to be gone.

v1.10.1

Changed

  • kt-user-menu takes block: at the foot of a sidebar, the account is a row shaped as the navigation items above it — their radius, padding and hover, the name and the email, a chevron — instead of a pill; the avatar alone, centred, in a rail; its menu as wide as the row.

Fixed

  • kt-header on a phone set its actions beside the brand, in the middle column the hidden navigation left empty. The brand opens the bar; the actions and the menu end it.
  • kt-app-shell on a phone kept its menu at the start of the header. Narrow, the menu ends the header, where a thumb reaches it; wide, the toggle still starts it, beside the sidebar it narrows.
  • kt-tour on a target as tall as the screen — a sidebar — set its card under it, off the screen. With no room above or below, the card goes beside it.

v1.10.0

Added

create-kanto — start a project on Kanto in one command: npm create kanto@latest.

  • Seven templates, in React, Vue and TypeScript without a framework, each a project of its own in templates/, to scaffold or to fork:
    • SaaS — sign in, first steps, a dashboard, customers and settings;
    • Dashboard — an admin console: an inbox, customers, files, an audit log, integrations and settings three levels deep, with Cmd+K;
    • Editor — documents in Markdown, with a preview, autosave and versions;
    • Landing — a product's marketing page, every section a block;
    • Chat — an assistant whose model is one function;
    • Portfolio — a person's work, career, writing and contact form;
    • Changelog — release notes by kind, with email and RSS.
  • Every template is installed against the published package, built and walked in a browser in CI and before every release.
  • The docs site's Templates section: a gallery, and a page per template with the template running in a frame, what to try, and the command for each stack. The Blocks page it replaces sent people to the same blocks as Components does; old links land on the first block.
  • The docs site's Apps section is a showcase of products built with Kanto, with a form to add yours. Its four demo applications are templates now — Dashboard, Landing, Chat and Portfolio — each running on its page; their old pages, and every screen of the console, lead there.

Fixed

  • kt-sub-menu-navigation, collapsed, hid each label and its name with it: an icon-only row read as a bare "link", with no tooltip to say where it led. Collapsed, a row is named by its label, read aloud and shown on hover.
  • kt-side-panel, closed, stayed in the page: slid off screen, its buttons were still in the tab order and in reach of screen readers. Closed, it is gone; it still slides in and out.
  • kt-tree narrower than a label let that row run past its edge, uncut; the label now ends in an ellipsis inside the tree.
  • kt-table narrower than its table could widen the page: an icon-only button in a cell, past the scroller's edge, kept its hidden label outside the scroll, so a phone scrolled sideways. The scroller now holds it.
  • The React types gave eight events only part of their detail: kt-table's kt-row-click lacked row, and kt-selection-change rows; the option, item or tab that kt-dropdown, kt-input-menu, kt-segmented-control, kt-split-button, kt-tabs and kt-breadcrumb send was missing too. A test now holds each event's type to the fields its element documents.
  • kt-chart with every value at zero ran its axis from −1 to 1, as if a count of nothing could go negative. An all-zero series runs from zero up.
  • kt-side-panel and kt-modal cut the focus ring of a field against the edge of their scrolling body. The body leaves the ring its room, and nothing in it moves.
  • kt-tour removed from the page while it updated tried to open its card off the page, and threw. It now does nothing until it is back.
  • kt-empty-state without an icon drew nothing: its default, inbox, was never registered. Kanto now ships it with the icons its elements draw.
  • kt-tour on a step whose target is out of view — in a closed drawer on a phone, say — set its card beside it, off the screen, where nothing could be read or clicked. The card now shows centred.
  • kt-table kept its 600px minimum in a narrow box on a wide window — half a laptop's page — and cut its last columns. It now reads its own width, not the window's.

v1.9.0

Added

kt-avatar-group — the people on something, as avatars overlapping in a row.

  • people is data; max shows that many and sums the rest up as "+N", said as "N more"; size sizes them all.

With React wrappers, React 19 JSX and Vue typings.

kt-copy-button — copies a value and says it did.

  • Built on kt-button's run(): the tick and "Copied" on success, announced; a shake, and no kt-copy, when the browser refuses.
  • icon-only, its own label and copied-label, the button's variant and size; copy() from script.

With React wrappers, React 19 JSX and Vue typings.

kt-description-list — terms and their details, the body of a detail page.

  • The items are data — list.items = [{ term, detail }] — drawn as a real <dl>; an empty detail shows a dash.
  • horizontal, each term beside its detail, or stacked, in up to four columns; both follow the list's own width. bordered rules each item.

With React wrappers, React 19 JSX and Vue typings.

kt-tour — a guided tour, a step at a time.

  • Each step lights its target, dims the rest, scrolls it into view and sets a card beside it — title, words, "Step 2 of 4", Previous, Next, Done; a step with no target centres its card.
  • Escape or the close button end it; kt-step, kt-finish and kt-close report it. The card is a dialog named by its title and takes the focus.

With React wrappers, React 19 JSX and Vue typings.

kt-accordion — kt-collapsible sections that open one at a time.

  • Opening one folds the one that was open, with the collapsible's own animation; multiple lets each open on its own. kt-change reports the open sections.
  • The arrows, Home and End move between the summaries. kt-collapsible gains focus(), which focuses its summary.

With React wrappers, React 19 JSX and Vue typings.

kt-tree — things inside things, as a tree to open and walk.

  • The nodes are data, as deep as they go; expanded and selected are yours to set. A click selects, the chevron opens and closes.
  • The WAI-ARIA tree view: one tab stop, the arrows through the visible nodes, Right and Left to open, close, enter and leave; each node says its level and place.

With React wrappers, React 19 JSX and Vue typings.

kt-user-menu — the account menu at the end of a header.

  • The avatar names the button; the panel shows who is signed in, then items with icons, separators and a danger colour, and a slot under them.
  • A menu button as WAI-ARIA describes it: the arrows, Home and End move between items; Escape closes and gives the focus back; Tab and a click outside close. Drawn in the top layer.

With React wrappers, React 19 JSX and Vue typings.

kt-app-shell — the frame of an application: a header, a sidebar and the content, which scrolls on its own.

  • Wide, the sidebar sits beside the content and its toggle narrows it to a rail (collapsed); narrower than breakpoint, measured on the shell itself, it becomes a drawer over the content, closed by Escape or a click outside, inert while closed.
  • The sidebar is an <aside> and the content a <main>; the toggle is named after the sidebar, with aria-expanded.
  • Sizes as custom properties; no-toggle for a header with its own control, no-landmarks for a shell shown inside a page that has its own.

With React wrappers, React 19 JSX and Vue typings.

kt-chat-message — one message of a conversation with an assistant.

  • An assistant's reply beside its avatar, as text on the page; a person's message in a bubble on the far side.
  • thinking shows three dots — and says so in words — until the reply has a word; streaming marks the end of a reply still arriving. Both set aria-busy, and hold still under reduced motion.
  • name, avatar, time and datetime; slots for the avatar and actions.

With React wrappers, React 19 JSX and Vue typings.

kt-color-picker — a colour, from swatches or set freely.

  • The swatches are a radio group — Kanto's accents until swatches gives others — and beside them the system picker and a hex field; no-custom keeps to the swatches.
  • A form control submitting #rrggbb; each swatch named by its colour.

With React wrappers, React 19 JSX and Vue typings.

kt-otp-input — a one-time code, one box per character.

  • Typing moves on, Backspace steps back, the arrows move between boxes, a pasted code is cleaned and spread; one-time-code lets a texted code fill itself in. kt-complete fires when the code is whole.
  • length, numeric or alphanumeric; a form control with required, error and reset. Each box is named by its place.

With React wrappers, React 19 JSX and Vue typings.

kt-prompt-input — the field a person writes to an assistant in.

  • Grows with what is written, up to max-rows, then scrolls; Enter sends and Shift+Enter starts a line, or submit-on="mod-enter" for Cmd or Ctrl+Enter.
  • kt-submit carries the text and wait(promise): the send button runs the request — its plane turning into the spinner and flying off — while the field holds the text, emptied on success, kept on failure.
  • Slots for attachments above the text and actions beside the button.

With React wrappers, React 19 JSX and Vue typings.

kt-footer — the site footer: the brand, columns of links and a legal line.

  • The columns are data — footer.columns = [{ heading, links }] — each link a label and an address; an external one is marked with an icon a screen reader skips.
  • Slots for the brand, a word under it, actions and the legal line, whose row is left out while empty.
  • variant="simple" runs the links along one row; heading-level, bordered and --kt-footer-max-width fit it to the page.
  • Lays itself out from its own width, side by side or stacked; a contentinfo landmark with its columns as one navigation.

With React wrappers, React 19 JSX and Vue typings.

  • kt-button runs an action and shows how it went. run(action) keeps the button busy while the action runs, then shows a tick — and done-label — or, if it throws, a shake, an alert and failed-label, and rests again two seconds later. It hands back what the action resolved or threw, and a second call while one runs gets the same promise. A send plane turns into the spinner itself — its outline drawn round into a turning arc — then back into the plane, which flies off to the top right; other icons give way to a spinner, and fade.

  • Instructions for AI agents. The package ships dist/AGENTS.md: the rules that make code right the first time, the mistakes agents make and the fix, and every component in a line. One line points Claude Code (@node_modules/kanto-ds/dist/AGENTS.md in CLAUDE.md), Cursor, Copilot or Codex at it. The docs site serves it as agents.md, beside a new llms-full.txt — every component page in one file — and a guide page for setting each agent up.

Blocks — a new family: whole sections of a page, built from the components, as ready to use as they are. Their words are their own — English until given others through texts — rather than in the shared strings, so they cost the other components nothing.

kt-auth-form — the way into a product, in four modes: sign-in, sign-up, forgot and code.

  • Checks what it can before sending and says so on the field; then kt-submit carries the mode, the values and wait(promise) — the button runs the request, and a rejection's message is shown above the fields.
  • Sign-in providers, a "keep me signed in" box, links between the modes (kt-mode), the autocomplete a password manager looks for.

With React wrappers, React 19 JSX and Vue typings.

kt-error-page — not found, broken, or down for maintenance, said plainly with the way on.

  • not-found leads home, error offers to try again — kt-retry, which reloads unless cancelled — and maintenance offers nothing it cannot do; code, an actions slot, fill, texts.

With React wrappers, React 19 JSX and Vue typings.

kt-hero — the top of a page: a heading, a line and the way in.

  • stacked puts the media under the words, split beside them where the hero is wide enough; align centres them or keeps them at the start.
  • Slots for an announcement, actions, a note and media; an empty one takes no room.

With React wrappers, React 19 JSX and Vue typings.

kt-feature-grid — what the product does, a feature at a time: an icon, a title and a line, in a grid.

  • The features are data; one with an href links from its title, and the whole feature answers it.
  • columns (2, 3 or 4) where there is room, fewer where there is not; variant="card" puts each on a card.

With React wrappers, React 19 JSX and Vue typings.

kt-cta — the ask at the end of a page: a heading, a line and the button that acts on them.

  • On a tinted panel by default, or plain; the actions under the words, or inline beside them where the block is wide enough.

With React wrappers, React 19 JSX and Vue typings.

kt-pricing-table — the plans side by side: what each costs, what it includes, and the way in.

  • A switch between monthly and yearly prices when the plans have both, with a note on what yearly saves; kt-billing says when it changes.
  • Prices formatted in currency and locale, or written as given — "Custom".
  • A featured plan is outlined and badged; a plan's button fires kt-plan, or is a link.

With React wrappers, React 19 JSX and Vue typings.

kt-testimonials — what the people who use it say: a quote, and who said it.

  • In packed columns, or single: each the page's width, in larger type.
  • Each a <figure> with its <blockquote>, and an avatar that falls back to initials.

With React wrappers, React 19 JSX and Vue typings.

kt-logo-cloud — who uses it, in their logos: a line of marks under a caption.

  • Images or inline SVGs, slotted, at one height and grey until hovered — colour keeps their colours.

With React wrappers, React 19 JSX and Vue typings.

kt-faq — the questions people ask before they sign up, answered: an accordion under a heading, one answer open at a time.

  • multiple lets each open on its own; open-first opens the first.
  • The head over the questions, or split beside them and held while they scroll; a note slot for where to ask the rest.

With React wrappers, React 19 JSX and Vue typings.

kt-post-grid — the latest posts, each a card that leads to it: a cover, a title, a line, and who wrote it when.

  • In columns where there is room, or a list with the cover beside the words; a more slot for a link to every post.
  • Dates written for people in locale, kept for machines in <time>.

With React wrappers, React 19 JSX and Vue typings.

kt-blog-post — a post, set to be read: its title, who wrote it and when, a cover, and the body in a measure an eye can follow.

  • The body's paragraphs, headings, lists, quotes, code and figures set as prose, in a column about 70 characters wide.
  • The reading time counted from the body; the date written for people and kept in <time>; tags and a link back to every post.

With React wrappers, React 19 JSX and Vue typings.

kt-newsletter — a way to hear from you again: a heading, a line, an email field and the button that subscribes it.

  • Checks the address, then kt-subscribe carries it with wait(promise): the button runs the request, and the block thanks the reader once it resolves — or says why not on the field.
  • On a tinted panel or plain; the field under the words or inline beside them.

With React wrappers, React 19 JSX and Vue typings.

kt-settings-section — one part of a settings page: what it is about, the fields that change it, and the button that saves them.

  • The heading and description beside the fields where there is room, or stacked over them; a footer for the actions and a note, shown only when given.
  • danger for the section that deletes things.

With React wrappers, React 19 JSX and Vue typings.

kt-empty-page — the page a new account lands on, before there is anything in it: a welcome, and the first steps that fill it.

  • Numbered steps with how far along it is; the next one's action is the primary button, a done one is ticked and read as done.
  • A step's action fires kt-step, or is a link.

With React wrappers, React 19 JSX and Vue typings.

Changed

  • The default icons include send and panel-left, which kt-prompt-input and kt-app-shell draw; a test now reads every icon the components draw from their sources and holds the defaults to them. Motion where something changes, and nowhere else: nothing moves when a page first appears, every duration comes from the theme's tokens, and all of it stops under prefers-reduced-motion.

  • A toast slides in from the screen edge and, closed, fades and folds away; the rest of the stack moves to make room or close the gap instead of jumping. toasts lists only the ones not on their way out.

  • kt-steps fills the line to the next step as one is completed, and empties it going back; the tick of a step just completed comes in with it.

  • kt-collapsible unfolds its content to its height, fading in, and folds it away before it closes — in every browser. A click half-way turns back from where it is.

  • kt-meter moves its fill to a new value, as kt-progress-bar does.

  • kt-tabs has one underline that slides from tab to tab, and kt-segmented-control one surface that slides from segment to segment, across or down.

  • Values that change under the reader's eyes say which way: kt-number-input rolls its number up as it grows and down as it shrinks, when stepped; kt-stat brings a new figure up into place; a box just checked in kt-checkbox, and the copy button of kt-code once it has copied, bring their tick in.

  • The docs site opens on a new home page: a live product screen under the headline, re-coloured from a row of swatches; three reasons, each shown working; the wall of screens; the blocks, by kind of page; every app as a live miniature; and the way in at the end. No code until it is asked for.

  • The docs site's Templates section is now Blocks: every block live on one page, grouped by the kind of page it belongs to — marketing, blog, application — each leading to its documentation. The five template screens it held are gone, the blocks do their work; an old link to them lands on the blocks.

Fixed

  • Four components read a custom property that does not exist, which made the whole declaration invalid without a word: kt-meter's and kt-timeline's labels and kt-page-header's section heading fell back to the inherited font, and kt-sub-menu-navigation's chevron turned with no easing. A test now holds every var() a component reads to a token or to a property it sets itself.
  • Links in the page took the accent's fill shade, 3.4:1 on the dark theme: a now takes --color-primary-text, made for text, and reads 4.5:1 in both themes.

v1.8.0

Added

kt-command-palette — the palette a product opens on Cmd+K.

  • Give it commands — a label, and optionally a group, an icon, keywords, a shortcut to show, a disabled state — and it lists them under their groups, narrows them as you type and runs the one you pick with kt-select.
  • The search takes every word typed, in any order, in the label, a keyword or the group, ignoring case and accents, and ranks a label that starts with it first. Ties keep the order you gave.
  • Cmd+K or Ctrl+K opens and closes it from anywhere; hotkey changes the letter or turns it off.
  • Built on the native <dialog>; the field is a combobox over a listbox, the focus goes in on opening and back where it was on closing.

With React wrappers, React 19 JSX and Vue typings.

kt-slider — a value picked by sliding: a volume, a threshold, a price range.

  • One thumb, or two with range; each is a native range input, so the keyboard and what a screen reader announces come from the platform.
  • The value is text — "40", or "20/80" for a range — kept within min and max and on step; two thumbs never cross.
  • show-value shows it above the track, through format when given one; kt-input fires while sliding, kt-change once let go.
  • A form control, with form.reset() and a disabled fieldset.

With React wrappers, React 19 JSX and Vue typings.

kt-steps — the steps of a flow and where the reader is in it.

  • Each step is a filled circle — a tick once done, its number otherwise — and steps before current are complete, the rest to come; a step with error shows where to go back to.
  • Across, the steps share the width, each label centred under its circle; down a rail with orientation="vertical", beside it.
  • With navigable, completed steps are buttons back to them — never forward.
  • An ordered list with aria-current="step", each state said in words.

With React wrappers, React 19 JSX and Vue typings.

kt-number-input — a number field with − and + beside it.

  • Typed the way the language writes a number — 1 234,5 in French — or stepped with the buttons and the arrows, Page Up and Down by ten, Home and End to the bounds; kept within min and max and on step.
  • At rest it reads through formatOptions: a currency, a percentage, a unit.
  • A spinbutton and a form control: required, error (announced), form.reset() and a disabled fieldset.

With React wrappers, React 19 JSX and Vue typings.

  • Templates on the docs site. Sign-in, dashboard, settings, onboarding and pricing: whole screens built from Kanto, each one source — markup, styles and a short script — shown running, copied as is, or opened on StackBlitz.
  • The docs site searches itself with Cmd+K: every guide, component, template, app and release page, and the site's own actions.

Changed

  • The axe suite waits for transitions inside shadow roots before it measures; dialogs were audited before their entrance had styled them.
  • The docs console runs its Cmd+K on kt-command-palette.
  • The docs site loads each demo app the first time it is opened, and offers to try again if the load fails.

v1.7.0

Added

  • A file for the CDN. dist/cdn/kanto.min.js is every element in one minified module, Lit and the default icons included, about 85 kB gzipped. The package's unpkg and jsdelivr fields point at it, so a page with no build step needs one script tag: <script type="module" src="https://cdn.jsdelivr.net/npm/kanto-ds@1"></script>. The release checks load it with nothing left to resolve.
  • For AI assistants. llms.txt at the docs site's root indexes Kanto for them — the rules that make code right the first time and every component's page — and each page now ships in the package as dist/docs/<tag>.md, where an assistant working in a project finds it.
  • The docs site opens on a home page: a wall of seventeen live product screens built from Kanto, re-themed from one row; a playground that re-themes a real screen and hands over the code, or opens it as a running project on StackBlitz; and answers to the questions people ask before they adopt a design system.
  • Every code block on the docs site is coloured, in colours that hold 4.5:1 in both themes.

Fixed

  • In the dark theme, an alert's action button in the alert's own tone — the example on the kt-alert page — read below 4.5:1 once the alert sat on a card: 4.11:1 for info, 4.45:1 for danger. --color-info-text and --color-danger-text are a shade lighter, and the accessibility suite now audits alerts with an action, on the page and on a card.
  • A closed floating panel near the right edge — a menu in a blurred header — no longer widens the page.

v1.6.1

Fixed

  • import 'kanto-ds' and import 'kanto-ds/vue' registered no element in a production build. The package told bundlers its entry points had no side effects, so Vite and webpack dropped them, and every element with them; a development server does not tree-shake, which is why it only showed once deployed. Both entries now declare their side effects, and every release builds a real application against the packed package before it ships.

Changed

  • The default icons gain the few a first screen reaches for — plus, pencil, settings, download, upload, external-link, filter, ellipsis, refresh-cw — so a button copied from the docs draws its icon without a registration step.

v1.6.0

Added

kt-multi-select — several choices from a list, typed to narrow it.

  • Each choice shows in the field as a removable chip. The field keeps to one line: the chips that do not fit collapse into a "+N", so it sits in a row of filters without changing its height, and grows back as the field widens.
  • The list stays open while options are ticked and unticked; Enter toggles the active one, Backspace in an empty field removes the last chip, and kt-filter reports the query for server-side lists.
  • A form control, submitted like <select multiple>: one entry per chosen id under name. required, error, form.reset() and a disabled fieldset.
  • An ARIA combobox over an aria-multiselectable listbox; the field's description says how many are chosen, since the chips may not all show. selectedCount joins KtStrings.

With React wrappers, React 19 JSX and Vue typings.

kt-date-input — a date typed into its parts, day, month and year.

  • The segments follow the reader's language — 25/09/2026 in London, 09/25/2026 in New York, 25.09.2026 in Berlin — and the focus moves on as each fills, so a date is eight keystrokes. The arrows step a segment, a day never past its month's end; a two-digit year is written out on leaving it.
  • The value is ISO 8601 and null until complete; kt-change fires once per date. A day that does not exist, or one outside min/max, shows the field's error tooltip.
  • calendar adds a button that opens a <kt-calendar> on the field's date.
  • A form control with required, error, form.reset() and a disabled fieldset. Each segment is a spinbutton with the numeric keypad on a phone; their names and placeholders join KtStrings.

With React wrappers, React 19 JSX and Vue typings.

kt-time-input — a time of day typed into its parts, hours and minutes.

  • The reader's clock — 14:30 in London, 02:30 PM in New York, 14.30 in Copenhagen — or the one hour-cycle names; on a 12-hour clock a or p sets the period. The value is always the 24-hour HH:MM that <input type="time"> uses.
  • seconds adds a segment and puts seconds in the value; step sets how many minutes the arrows move by; a time outside min/max shows the field's error tooltip.
  • The same keyboard, form behaviour and spinbutton segments as kt-date-input, from a base the two share.

With React wrappers, React 19 JSX and Vue typings.

Accent colour. The primary colour is now a choice: seven presets set with data-accent="blue" on the page or a container, or any colour through setAccent('#e11d48'). Contrast is solved for each colour in OKLCH — white on a primary fill and the accent as text both hold 4.5:1 in both themes — and the neutral surfaces take a trace of its hue. accentPalette() returns the values without applying them. The docs site has a Customise menu in its top bar.

Appearance. Font, corners, density and text size join the accent as settings: data-font, data-radius, data-density and data-text-size on the page or a container, or setAppearance({ … }) for all of them with the theme and accent. Density and text size compose with the responsive scales; the defaults render as before.

Changed

  • kt-calendar's title opens one panel of years and months, instead of a month grid and a separate grid of twelve years paged twelve at a time. The years scroll beside the months of the one chosen, bounded by min and max; typing four digits jumps to a year, Page Up and Down move by ten. Reaching a birth year is two clicks, or the year typed and one click. The header reads "September 2026" as one control with the arrows grouped beside it, the days are 32px, and today is a dot under its number. KtCalendarView is now 'day' | 'month'.
  • kt-date-picker takes a typed date, read the way the reader writes it — 25/09/2026, 9/25/2026, 25 sept., ISO, the year optional — and a period as two dates around a dash. A date that does not read, or falls outside min/max, is flagged as kt-input flags an error, and reported as badInput. The field is a text field now, the calendar button leading it. Parts: field, input, trigger (the button), panel, presets, calendar.
  • With range, kt-date-picker offers ready-made periods — today, the last 7 and 30 days, this month and last, this year — beside two months; presets replaces them, [] hides them. A single date gets a Today button. On screens under 720px it shows one month, the periods above it.
  • Popovers stand out in the light theme. Every list, menu and calendar that opens over the page — kt-select, kt-input-menu, kt-multi-select, kt-dropdown, kt-date-picker, kt-input's country picker — sat on a grey a step darker than the page, where a hover or a period's band barely showed. They now sit on white with an edge and a soft shadow, through new tokens: --surface-popover, --border-popover, --shadow-popover, --divider-popover. The dark theme looks as it did. A period's band in the calendar is a clearer tint.
  • kt-calendar gains months="2".
  • Floating panels are placed again when their size changes, and keep the width of their content wherever they are placed: a period picker no longer runs off the screen once its months have drawn.
  • kt-radio's dot is centred on whole pixels in every browser; it sat off centre in Firefox.

v1.5.2

Added

  • The documentation site flags components added in the last two months as "New" in its navigation, read from this changelog's Added entries. The package itself is unchanged from 1.5.1.

v1.5.1

1.5.0 was tagged but never reached npm: a browser test failed on CI and the publish stopped before uploading. 1.5.1 is the first release on npm with everything listed under 1.5.0.

Fixed

  • The development toolchain resolved brace-expansion 5.0.9, flagged for denial of service by crafted brace patterns (GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p). It is now 5.0.12. It only reached the repository through ESLint, so the published package was never exposed and its contents are unchanged.
  • The browser test of kt-segmented-control's labels counted the boxes of the whitespace around a label as lines, which read as a wrapped label on CI's fonts. It now counts the label's own text only.

v1.5.0

Added

kt-date-picker — a day or a period, in a field that opens a calendar.

  • The value is ISO 8601 text: 2026-09-25, or 2026-09-01/2026-09-25 with range. The field shows it formatted for the reader through Intl.
  • The WAI-ARIA date picker dialog pattern: one tab stop in the grid, arrows by day and week, Page Up/Down by month (with Shift, by year), Enter to choose, Escape to close with focus back on the field. A period takes two picks, in either order, and fires kt-change once.
  • A form control: ISO value, nothing for an incomplete period, required, error, form.reset(). min and max days can be reached but not chosen.

kt-calendar — the calendar on its own, for when it is the interface.

  • The title's month and year are buttons that open a grid of months or of twelve years, so a date decades away is three or four clicks.
  • Month and weekday names, and the first day of the week, follow the locale: locale, else the page's lang, else the browser's.

kt-table for data sorted and paged on a server.

  • manual: data is the page the server sent, shown as it is, and total-rows is what the pager counts from. Headers and the pager still fire kt-sort-change and kt-page-change; the application loads what they ask for. A new sort sends the table back to page 1 with a single event.
  • While loading, a manual table keeps the page on screen, dimmed and aria-busy, so the pager keeps its place and its focus.
  • page is a public property in both modes.
  • locale sorts text in a given language; by default the page's lang, then the browser's.
  • virtual renders only the rows in view, so thousands of rows scroll without paging, under a sticky header. Rows must share one height; aria-rowcount and aria-rowindex keep screen readers counting the whole table.

kt-input phone numbers.

  • A number typed with the country's trunk prefix — 06 12 34 56 78 after +33 — puts the field in its error state and reports a patternMismatch to the form. KtCountry gains an optional trunkPrefix, and KtStrings a phoneTrunkPrefix message.

  • About 95 countries ship by default, sorted by name, up from six. Search ignores accents and matches dial codes from their start.

  • The error message, the password toggle and the clear button each show a tooltip. The error's also shows while the field has focus, for keyboard users, until Escape puts it away.

  • A --duration-slow token (0.4s, zero under reduced motion).

Changed

  • kt-tooltip draws its bubble in the top layer, as a popover placed from the trigger. No ancestor's overflow clips it any more, and it takes no room in the page. placement is now a preference: the bubble takes the opposite side when that one has no room, and stays inside the screen. Scrolling or resizing hides it. Long text wraps at 260px instead of running past it. A new open property holds the bubble open, for a trigger that cannot take focus.
  • kt-input groups a phone number the way its country's format does, so what is typed matches the placeholder — a German number reads 1234 567890 rather than in threes. The US placeholder is 201-555-0123.
  • kt-table sorts once per change of data, order or locale rather than on every render. Replace data with a new array; changing it in place is not seen. Its sort indicator is the Lucide arrow-up or arrow-down icon.
  • The panels of kt-select, kt-input-menu, kt-date-picker, kt-dropdown and kt-input's country picker open in the top layer, like kt-tooltip's bubble. A container with overflow: hidden — a table cell, a side panel — no longer cuts them off. Each opens below its trigger, or above it when there is no room below, and follows the trigger as the page scrolls.
  • kt-select, kt-input-menu, kt-date-picker, kt-dropdown and kt-input's country panel listen on the document only while they are open.
  • kt-calendar makes each date format once instead of on every render.

Fixed

  • kt-input in phone mode submitted the national digits alone (612345678) instead of the international number (+33612345678), and a restored page lost the country.
  • A <fieldset disabled> disabled none of the form controls inside it. All ten now follow it, and re-enabling the fieldset leaves a control its author disabled as it was.
  • kt-input showed the US phone format as the placeholder of any field without one, an email field included.
  • The search box of kt-input's country panel shrank while the list below it overflowed.
  • A hidden kt-tooltip by the right edge of the page widened the document and scrolled it sideways.
  • kt-segmented-control split a label over two lines of its fixed-height segment when its container shrank to fit.
  • kt-toast, kt-progress-bar, kt-table and kt-tooltip used literal colours or durations: the toasts' tints did not follow the theme, and the row hover and tooltip fade ignored reduced motion.

v1.4.0

Added

kt-checkbox — a choice that applies when the form is submitted.

  • A real <input type="checkbox"> sits invisibly over the drawn box, so Space, the click on the label, the indeterminate state and what a screen reader announces all come from the platform.
  • indeterminate draws a dash for a "select all" over a partial selection; the first click clears it, as on a native checkbox.
  • Three sizes, off the button heights like kt-toggle.
  • A form control: its value under name when checked, nothing otherwise; required, error and form.reset().

kt-radio-group with kt-radio options — one choice among a few, all visible.

  • Native radios cannot form a group across shadow roots, so this is the ARIA radio group pattern: one tab stop, landing on the chosen option; arrow keys move and select, wrapping and skipping disabled options; Space selects.
  • The group owns the value and is the form control: name, required, error, form.reset(). orientation="horizontal" lays the options in a row.

Both ship with React 18 wrappers, React 19 JSX and Vue typings.

  • checkRequired in KtStrings, the message of a required checkbox left unchecked. The minus icon joins the defaults, for the indeterminate state.

Fixed

  • Four strings still fell back to English behind || and ??: the toggle-button group's "Options", the chart data table's "Chart data" caption and "Value" column, and kt-form's "Form section". They are now options, chartData, chartValue and formSection in KtStrings.

v1.3.0

Added

Every string the elements write can be translated. The README promised that every default string was a property. About twenty were not — validation messages, "Previous" and "Next", the headers of the table every chart renders, and accessible names like "Clear" and "Dismiss" that only a screen reader hears — and could not be changed at all. They now all come from one registry:

import { setStrings } from 'kanto-ds/strings'; setStrings({ clear: 'Effacer', noData: 'Aucune donnée', pageOf: (page, total) => `Page ${page} sur ${total}`, });
  • Anything left out stays in English. KtStrings lists every key.
  • Switching at runtime re-renders every element, validation messages included.
  • Strings that carry a value are functions, so a translation owns the word order.
  • Text properties — emptyText, placeholder, confirmLabel… — still win for the one element they are set on, an empty string included. Their default is now undefined, meaning "from the registry".
  • setStrings, resetStrings, getStrings and defaultStrings are exported from kanto-ds and from kanto-ds/strings, which loads no element.

Changed

  • No more flash of raw content. Until its script loads, a kt-* tag is an unknown element whose slotted text shows unstyled, then jumps into place. base.css (part of styles.css) now keeps Kanto's tags hidden while they are :not(:defined), with visibility so the layout holds.

v1.2.0

React 19 is now the first-class path: it renders the kt-* tags directly, so it needs no wrapper and no extra package. The React 18 wrappers stay, and stop costing everyone else a dependency.

Changed

  • @lit/react is no longer installed with kanto-ds. It is an optional peer dependency, needed only by the wrappers in kanto-ds/react. On React 18, install it alongside: npm install @lit/react. Without it, importing kanto-ds/react fails to resolve. Vue, Angular, Svelte and React 19 projects never install it.
  • react-dom is no longer listed as an optional peer dependency: nothing in Kanto imports it.

Added

  • kanto-ds/react/jsx, JSX typings for React 19. Without it TypeScript rejects every kt-* tag; with it, each tag takes its own properties and a typed handler for each event it fires — onkt-change={(e) => e.detail.value}. Types only: importing it loads nothing.

Fixed

  • The docs said Kanto ships seventeen default icons; it ships 22.

v1.1.0

Added

  • Editor support. The package ships a Custom Elements Manifest (kanto-ds/custom-elements.json) describing every element's attributes, properties, events, slots and CSS parts, and the editor data generated from it: web-types.json, which JetBrains IDEs pick up on their own, and VS Code's HTML and CSS custom data. Plain HTML gets completion and hover docs; the README has the one-line VS Code setting.

  • popup and expanded on kt-button, passed to its inner button as aria-haspopup and aria-expanded. kt-dropdown sets them on its trigger; set them yourself when a button opens something else.

  • A -text step for every brand and semantic colour — --color-primary-text, --color-success-text, --color-warning-text, --color-danger-text, --color-info-text — for text and icons, and --color-danger-solid for a filled destructive button.

Fixed

Every text colour meets WCAG AA in both themes. An axe audit of every element, one case per tone and variant, found 21 places under 4.5:1 — the light success badge sat at 1.58:1. All pass now.

  • Text and icons use the new -text steps; fills keep -base, so tinted backgrounds and solid buttons look as they did.
  • --color-primary-base is a shade darker (#5f5dea) so white text on a primary button clears 4.5:1, and --color-primary-hover darkens instead of lightening, for the same reason. The delete button moves to --color-danger-solid.
  • --color-text-400, behind --text-muted, lifts from #9191a1 to #a9a9b5 in dark. Labels, eyebrows and placeholders that were set in --color-text-500 use --text-muted.
  • Avatar initials mix the person's hue with the body text, so all eight hues read on their tint.

Every field is a form control, as 1.0.0 promised. kt-input-menu, kt-drag-drop and kt-segmented-control held a value but never reached the form around them, so a submission silently left them out. They are now form-associated, like the other fields: serialised into FormData under name, validated with required, and reset by form.reset().

  • kt-input-menu submits the chosen option's id, never its label.
  • kt-drag-drop submits every held file under its name, exactly as <input type="file" multiple> would, so a multipart/form-data post works with no extra code. Without a name it submits nothing, like the native input.
  • kt-segmented-control submits its value.
  • A disabled kt-segmented-control ignores the keyboard. It was dimmed and ignored the pointer, but its segments kept their tab stop, and the arrow keys still changed the value.

Screen readers hear what the screen shows. An axe audit of every element in both themes, in a real browser, found these:

  • kt-toggle had no accessible name when its label was slotted: the switch and the text beside it were not connected, so it was announced as an unnamed switch.
  • kt-dropdown announced its state nowhere. aria-expanded sat on a wrapper that never takes focus, where it is not even allowed. It now goes on the trigger itself, and aria-haspopup="menu" only when the panel is a menu — a panel of free content is a plain disclosure. kt-split-button's caret follows.
  • kt-input's error message was never read. aria-errormessage held the message text instead of an element id, so "Enter an email." was taken for three ids that do not exist. The message is now linked with aria-describedby, as it newly is on kt-textarea, which had no link at all.

Changed

v1.0.1

Added

  • kt-split-button, a primary action with a menu of related ones hanging off a caret. A caret press never surfaces as a click on the host, so one click listener means the primary action and nothing else.
  • align on kt-dropdown, lining the panel up with the trigger's right edge instead of its left. Defaults to the existing behaviour.

Changed

kt-chart covers every common chart type. It drew line, area and bar; it now also draws horizontal, stacked and mixed bar-and-line charts, scatter, bubble, pie, doughnut, polar area and radar — one element, one type attribute, one data model. Existing charts keep working unchanged.

  • A value axis on every chart, with ticks on round steps, formatted by format. min and max pin it; no-axis hides it for small charts.
  • A tooltip on every type, bars and slices included. It opens beside a mark when there is no room above it, never on top of it.
  • The legend is a set of toggles. Pressing an item hides the series, or the slice, and rescales the chart; the other colours stay where they were. kt-series-toggle reports it and can be cancelled.
  • Keyboard access. The plot is focusable; arrow keys, Home, End and Escape walk the points, and the tooltip announces each one.
  • smooth is a true monotone spline. It was documented as one and drew a curve with flat tangents at every sample.
  • New properties: stacked, horizontal, min, max, no-axis, formatX, center-label, total-label. KtSeries gains points and type; KtPoint and KtMark are exported. kt-point-hover adds series to its detail.

The rules that made the chart honest still hold, and now cover the new types: one value axis, bars and areas from zero, bubbles and polar slices sized by area, slices read from a single series, the eight categorical hues in fixed order, and the data always rendered as a table.

Fixed

  • Bar charts no longer fuse into a wall. Bars filled their whole band with no gap between categories; groups now take 64% of it.
  • Category labels sit under their bars. They were placed with a line chart's geometry, so the first hung off the left edge and the last was clipped.
  • Bars are square at the baseline and rounded only at the tip, so they stand on the axis instead of floating above it.

v1.0.0

First release of Kanto, a design system for data-dense product interfaces: one implementation, built on standard custom elements, that runs wherever HTML does.

Added

Token layer — colours, typography, spacing, motion and z-index as CSS custom properties. Dark is the default; light and auto remap the same names, so no component carries theme-specific CSS. Responsive scales for mobile, tablet and ultra-wide, plus a prefers-reduced-motion block that zeroes every duration in the system at once.

42 elements across six groups, each in a folder of its own holding the element, its tests and its documentation:

  • Core — kt-button, kt-card, kt-code, kt-icon, kt-avatar, kt-badge, kt-kbd
  • Forms — kt-form, kt-input (with phone mode), kt-textarea, kt-label-input, kt-select, kt-input-menu, kt-toggle, kt-drag-drop
  • Navigation — kt-header, kt-breadcrumb, kt-sub-menu-navigation, kt-page-header, kt-tabs, kt-segmented-control, kt-toggle-button, kt-toggle-button-group
  • Feedback — kt-progress-bar, kt-skeleton, kt-tooltip, kt-toast, kt-toast-container, the toaster singleton, kt-alert, kt-empty-state
  • Overlays — kt-dropdown, kt-side-panel, kt-confirm-dialog, kt-modal, kt-collapsible
  • Data — kt-table, kt-pagination, kt-stat, kt-chart, kt-meter, kt-timeline (with kt-timeline-item)

Some of these earn their separation. kt-tabs and kt-segmented-control look alike and mean different things: a segmented control picks a value and belongs in a form or a toolbar, tabs switch which region of the page you are looking at. kt-meter and kt-progress-bar are the same trap: a progress bar answers "how far along" and turns green at the end, a meter answers "how is it divided" and is full from the moment it renders. kt-form is a <fieldset> rather than a <form>, because a form-associated control finds its form by walking its own tree and a <form> inside a shadow root would own nothing slotted into it. kt-code owns the chrome around a block and leaves highlighting to the caller.

Form participation — every field is a form-associated custom element: serialised into FormData, reset by form.reset(), validated by checkValidity().

Framework entry points — kanto-ds/react for typed wrappers, kanto-ds/vue for the compiler predicate and template typings. Angular and Svelte need neither.

A documentation site under demo/, built with the design system and no framework: a filterable sidebar, a table of contents that follows the scroll, and a page per component rendered from that component's own markdown, so the docs cannot drift from the file beside the source. Every component has a live preview rather than a screenshot.

Four full applications under #/app/…, rendered without the documentation chrome because an application shell wrapped in another application shell reads as neither. Each has a documentation page that embeds it, running, rather than a screenshot:

  • Console — an eleven-screen operations product sharing one shell: a Home with a date range, a stat row and a linked chart; a two-pane Inbox; a Customers table with filters, a bulk bar, a detail panel and a confirm dialog; Files; an Activity log; Integrations; and Settings in five sub-sections, two of them three levels down. A ⌘K palette, a storage meter and a notification bell sit in the chrome, and the whole sidebar — nesting included — is <kt-sub-menu-navigation> rather than markup written for the screen.
  • Landing page — hero, chart, features, a monthly/annual pricing toggle, an FAQ and a sign-up band.
  • Assistant — a thread list, a transcript with streaming replies, the tool calls that produced each answer, and a sources rail that follows the turn being read.
  • Portfolio — filterable work, a case-study panel, a career timeline and a contact form.

They are the reason several elements above exist. A gallery proves a button renders; only a whole screen shows what happens when forty of them share one.

Conventions

Decisions that hold across the system, stated once because every element depends on them:

  • Sizes are small/medium/large everywhere. One scale for every element, rather than a short spelling for fields and a long one elsewhere.
  • Attribute values are lowercase and hyphenated — secondary-no-bg, never secondaryNoBg. HTML authors attribute values in lowercase, so a camelCase spelling silently fails half the time.
  • Phone mode requires type="tel". Sniffing the placeholder, the name or the icon for /tel|phone/ would turn a field named telephone_verifie into a country picker.
  • A phone field submits the full international number, not bare national digits — posting 612345678 with no country throws away what the picker exists to capture.
  • heading, not title, on kt-toast, kt-side-panel and kt-drag-drop. Every element already has a title, and shadowing it puts the text in a native tooltip.
  • Icons come from the lucide package, through a registry, rather than a UMD <script> and a global.
  • Component CSS lives in static styles, not in strings injected into <head> on first render.

Fixed

  • Sortable table headers are buttons. They were clickable <th>s with a tabindex and no key handler, so the table could not be sorted by keyboard.
  • The segmented control is a radio group. It rendered N independent buttons: five options meant five tab stops and no arrow keys.
  • Clickable cards and badges are real controls — role="button", a tab stop, Enter and Space.
  • The side panel and confirm dialog are native <dialog>s. As fixed divs, Tab walked straight out of the panel into the page behind it.
  • The confirm dialog focuses Cancel, so a stray Enter cannot delete anything.
  • Error toasts interrupt (role="alert"); they were polite statuses that could sit unread.
  • Progress bars clamp their value. Dividing by an unguarded max rendered width: NaN%.
  • Category badges tint through color-mix instead of concatenating "33" onto a hex string, which only ever worked for six-digit hex.
  • Table sorting is locale-aware — "Ångström" files next to "Angstrom", and "Entity 2" precedes "Entity 10". Empty values sort last in both directions.
  • Combobox arrows walk the filtered list. They indexed into the unfiltered array, so pressing Down after typing selected whatever sat at that index.
  • Modified clicks on links are left alone, so ⌘-click on a breadcrumb opens a tab.
  • aria-current marks the active navigation item. The highlight was purely visual.
  • font-display: swap — the dashboard rendered invisible text for the whole webfont fetch.

Copy

All copy is English — placeholders, empty and loading states, validation messages and every generated aria-label. Each of those strings is a property, so an application in another language overrides it at the call site rather than forking a component.