Releases
The current release is v1.12.0, from 26 October 2026.
Current release
History
From the changelog, because GitHub could not be reached from this browser.
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.
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-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.
create-kanto — start a project on Kanto in one command: npm create kanto@latest.
kt-avatar-group — the people on something, as avatars overlapping in a row.
kt-command-palette — the palette a product opens on Cmd+K.
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.
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.
kt-multi-select — several choices from a list, typed to narrow it.
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.
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.
kt-date-picker — a day or a period, in a field that opens a calendar.
kt-checkbox — a choice that applies when the form is submitted.
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:
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.
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.
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.
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, withsitemap.xmlandrobots.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-paneltakessize: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-messagetakescontinued: 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-menutakesblock: 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-headeron 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-shellon 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-touron 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-treenarrower than a label let that row run past its edge, uncut; the label now ends in an ellipsis inside the tree.kt-tablenarrower 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'skt-row-clicklackedrow, andkt-selection-changerows; theoption,itemortabthatkt-dropdown,kt-input-menu,kt-segmented-control,kt-split-button,kt-tabsandkt-breadcrumbsend was missing too. A test now holds each event's type to the fields its element documents. kt-chartwith 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-panelandkt-modalcut 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-tourremoved 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-statewithout anicondrew nothing: its default,inbox, was never registered. Kanto now ships it with the icons its elements draw.kt-touron 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-tablekept 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.
peopleis data;maxshows that many and sums the rest up as "+N", said as "N more";sizesizes 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'srun(): the tick and "Copied" on success, announced; a shake, and nokt-copy, when the browser refuses. icon-only, its ownlabelandcopied-label, the button'svariantandsize;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, orstacked, in up to fourcolumns; both follow the list's own width.borderedrules 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-finishandkt-closereport 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;
multiplelets each open on its own.kt-changereports the open sections. - The arrows, Home and End move between the summaries.
kt-collapsiblegainsfocus(), 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;
expandedandselectedare 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 thanbreakpoint, 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, witharia-expanded. - Sizes as custom properties;
no-togglefor a header with its own control,no-landmarksfor 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.
thinkingshows three dots — and says so in words — until the reply has a word;streamingmarks the end of a reply still arriving. Both setaria-busy, and hold still under reduced motion.name,avatar,timeanddatetime; 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
swatchesgives others — and beside them the system picker and a hex field;no-customkeeps 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-codelets a texted code fill itself in.kt-completefires when the code is whole. length,numericoralphanumeric; a form control withrequired,errorand 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, orsubmit-on="mod-enter"for Cmd or Ctrl+Enter. kt-submitcarries the text andwait(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; anexternalone 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,borderedand--kt-footer-max-widthfit 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-buttonruns an action and shows how it went.run(action)keeps the button busy while the action runs, then shows a tick — anddone-label— or, if it throws, a shake, an alert andfailed-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. Asendplane 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.mdinCLAUDE.md), Cursor, Copilot or Codex at it. The docs site serves it asagents.md, beside a newllms-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-submitcarries the mode, the values andwait(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), theautocompletea 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-foundleads home,erroroffers to try again —kt-retry, which reloads unless cancelled — andmaintenanceoffers nothing it cannot do;code, anactionsslot,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.
stackedputs the media under the words,splitbeside them where the hero is wide enough;aligncentres 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
hreflinks 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
panelby default, orplain; the actions under the words, orinlinebeside 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-billingsays when it changes. - Prices formatted in
currencyandlocale, 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 —
colourkeeps 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.
multiplelets each open on its own;open-firstopens the first.- The head over the questions, or
splitbeside them and held while they scroll; anoteslot 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
columnswhere there is room, or alistwith the cover beside the words; amoreslot 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-subscribecarries it withwait(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
panelorplain; the field under the words orinlinebeside 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
stackedover them; a footer for the actions and a note, shown only when given. dangerfor 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
sendandpanel-left, whichkt-prompt-inputandkt-app-shelldraw; 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 underprefers-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.
toastslists only the ones not on their way out.kt-stepsfills 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-collapsibleunfolds 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-metermoves its fill to a new value, askt-progress-bardoes.kt-tabshas one underline that slides from tab to tab, andkt-segmented-controlone surface that slides from segment to segment, across or down.Values that change under the reader's eyes say which way:
kt-number-inputrolls its number up as it grows and down as it shrinks, when stepped;kt-statbrings a new figure up into place; a box just checked inkt-checkbox, and the copy button ofkt-codeonce 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 andkt-timeline's labels andkt-page-header's section heading fell back to the inherited font, andkt-sub-menu-navigation's chevron turned with no easing. A test now holds everyvar()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:
anow 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 withkt-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;
hotkeychanges 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 withinminandmaxand onstep; two thumbs never cross. show-valueshows it above the track, throughformatwhen given one;kt-inputfires while sliding,kt-changeonce 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
currentare complete, the rest to come; a step witherrorshows 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,5in French — or stepped with the buttons and the arrows, Page Up and Down by ten, Home and End to the bounds; kept withinminandmaxand onstep. - 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.jsis every element in one minified module, Lit and the default icons included, about 85 kB gzipped. The package'sunpkgandjsdelivrfields 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.txtat 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 asdist/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-alertpage — read below 4.5:1 once the alert sat on a card: 4.11:1 for info, 4.45:1 for danger.--color-info-textand--color-danger-textare 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'andimport '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-filterreports the query for server-side lists. - A form control, submitted like
<select multiple>: one entry per chosen id undername.required,error,form.reset()and a disabled fieldset. - An ARIA combobox over an
aria-multiselectablelistbox; the field's description says how many are chosen, since the chips may not all show.selectedCountjoinsKtStrings.
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/2026in London,09/25/2026in New York,25.09.2026in 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
nulluntil complete;kt-changefires once per date. A day that does not exist, or one outsidemin/max, shows the field's error tooltip. calendaradds 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 joinKtStrings.
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:30in London,02:30 PMin New York,14.30in Copenhagen — or the onehour-cyclenames; on a 12-hour clockaorpsets the period. The value is always the 24-hourHH:MMthat<input type="time">uses. secondsadds a segment and puts seconds in the value;stepsets how many minutes the arrows move by; a time outsidemin/maxshows 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 byminandmax; 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.KtCalendarViewis now'day' | 'month'.kt-date-pickertakes 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 outsidemin/max, is flagged askt-inputflags an error, and reported asbadInput. 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-pickeroffers ready-made periods — today, the last 7 and 30 days, this month and last, this year — beside two months;presetsreplaces 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-calendargainsmonths="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-expansion5.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, or2026-09-01/2026-09-25withrange. The field shows it formatted for the reader throughIntl. - 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-changeonce. - A form control: ISO value, nothing for an incomplete period,
required,error,form.reset().minandmaxdays 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'slang, else the browser's.
kt-table for data sorted and paged on a server.
manual:datais the page the server sent, shown as it is, andtotal-rowsis what the pager counts from. Headers and the pager still firekt-sort-changeandkt-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 andaria-busy, so the pager keeps its place and its focus. pageis a public property in both modes.localesorts text in a given language; by default the page'slang, then the browser's.virtualrenders only the rows in view, so thousands of rows scroll without paging, under a sticky header. Rows must share one height;aria-rowcountandaria-rowindexkeep screen readers counting the whole table.
kt-input phone numbers.
A number typed with the country's trunk prefix —
06 12 34 56 78after +33 — puts the field in its error state and reports apatternMismatchto the form.KtCountrygains an optionaltrunkPrefix, andKtStringsaphoneTrunkPrefixmessage.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-slowtoken (0.4s, zero under reduced motion).
Changed
kt-tooltipdraws its bubble in the top layer, as a popover placed from the trigger. No ancestor'soverflowclips it any more, and it takes no room in the page.placementis 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 newopenproperty holds the bubble open, for a trigger that cannot take focus.kt-inputgroups a phone number the way its country'sformatdoes, so what is typed matches the placeholder — a German number reads1234 567890rather than in threes. The US placeholder is201-555-0123.kt-tablesorts once per change ofdata, order or locale rather than on every render. Replacedatawith a new array; changing it in place is not seen. Its sort indicator is the Lucidearrow-uporarrow-downicon.- The panels of
kt-select,kt-input-menu,kt-date-picker,kt-dropdownandkt-input's country picker open in the top layer, likekt-tooltip's bubble. A container withoverflow: 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-dropdownandkt-input's country panel listen on the document only while they are open.kt-calendarmakes each date format once instead of on every render.
Fixed
kt-inputin 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-inputshowed 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-tooltipby the right edge of the page widened the document and scrolled it sideways. kt-segmented-controlsplit a label over two lines of its fixed-height segment when its container shrank to fit.kt-toast,kt-progress-bar,kt-tableandkt-tooltipused 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. indeterminatedraws 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
valueundernamewhen checked, nothing otherwise;required,errorandform.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.
checkRequiredinKtStrings, the message of a required checkbox left unchecked. Theminusicon 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, andkt-form's "Form section". They are nowoptions,chartData,chartValueandformSectioninKtStrings.
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:
- Anything left out stays in English.
KtStringslists 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 nowundefined, meaning "from the registry". setStrings,resetStrings,getStringsanddefaultStringsare exported fromkanto-dsand fromkanto-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 ofstyles.css) now keeps Kanto's tags hidden while they are:not(:defined), withvisibilityso 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/reactis no longer installed with kanto-ds. It is an optional peer dependency, needed only by the wrappers inkanto-ds/react. On React 18, install it alongside:npm install @lit/react. Without it, importingkanto-ds/reactfails to resolve. Vue, Angular, Svelte and React 19 projects never install it.react-domis 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 everykt-*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.popupandexpandedonkt-button, passed to its inner button asaria-haspopupandaria-expanded.kt-dropdownsets them on its trigger; set them yourself when a button opens something else.A
-textstep 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-solidfor 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
-textsteps; fills keep-base, so tinted backgrounds and solid buttons look as they did. --color-primary-baseis a shade darker (#5f5dea) so white text on a primary button clears 4.5:1, and--color-primary-hoverdarkens instead of lightening, for the same reason. The delete button moves to--color-danger-solid.--color-text-400, behind--text-muted, lifts from#9191a1to#a9a9b5in dark. Labels, eyebrows and placeholders that were set in--color-text-500use--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-menusubmits the chosen option'sid, never its label.kt-drag-dropsubmits every held file under itsname, exactly as<input type="file" multiple>would, so amultipart/form-datapost works with no extra code. Without anameit submits nothing, like the native input.kt-segmented-controlsubmits itsvalue.- A disabled
kt-segmented-controlignores 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-togglehad 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-dropdownannounced its state nowhere.aria-expandedsat on a wrapper that never takes focus, where it is not even allowed. It now goes on the trigger itself, andaria-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-errormessageheld 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 witharia-describedby, as it newly is onkt-textarea, which had no link at all.
Changed
homepageinpackage.jsonpoints to the documentation site, https://kanto.arnaudmichel.fr, instead of the README.
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 aclickon the host, so oneclicklistener means the primary action and nothing else.alignonkt-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.minandmaxpin it;no-axishides 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-togglereports 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.
smoothis 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.KtSeriesgainspointsandtype;KtPointandKtMarkare exported.kt-point-hoveraddsseriesto 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, thetoastersingleton,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(withkt-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
⌘Kpalette, 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/largeeverywhere. 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, neversecondaryNoBg. 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 namedtelephone_verifieinto a country picker. - A phone field submits the full international number, not bare national
digits — posting
612345678with no country throws away what the picker exists to capture. heading, nottitle, onkt-toast,kt-side-panelandkt-drag-drop. Every element already has atitle, and shadowing it puts the text in a native tooltip.- Icons come from the
lucidepackage, 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
maxrenderedwidth: NaN%. - Category badges tint through
color-mixinstead 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-currentmarks 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.