What's new
What arrived in each version, taken from the changelog (CHANGELOG.md) that ships in the 0.17.1 package. An entry marked Breaking changes something your code has to adjust when upgrading: npx amset-ui migrate takes care of almost all of it.
0.17.1, released October 19, 2026
Version 0.17.0 was published from aprepipeline that predated theFlagfix, and it shipped with that bug: with SSR, the home page and any page with a flag gave error 418 on hydration. If you useFlagwith a server, upgrade to this one.
Flagno longer breaks hydration when the server already had its region (AMS-1894). A static build renders hundreds of pages in the same process, and from the second one on the flag region was already in the server cache:Flagrendered it without aSuspenseboundary, the browser (which starts from scratch) rendered it with one, and React threw error 418 on hydration. It happened on the amset.tech home page and on every page with the language picker. Now the boundary is always there and the browser keeps what the server rendered until the region arrives; a real hydration with a warm server and a cold browser tests it.migrateanddoctorsee the old stylesheets imported from CSS (AMS-1912).migratealso rewrites the@import '@amset/ui/estilos.css'andestilos/<hoja>.csslines of your .css files, anddoctorreports an error if the global CSS imports a stylesheet that no longer exists (before, it said you imported none).doctornow counts the same namesmigratechanges, including the 0.16 ones, and flagsdata-tema, which the library no longer reads.npx amset-ui create: a new application with the library already set up (AMS-1786). Two templates instarters/: Next with App Router (the default) and Vite with--template vite. Strict TypeScript and ESLint out of the box, no Tailwind, the shell (AppShell,TopBar,ThemeButtonandSideNavmarking the current page) and each page in its own file: in Next, server pages; in Vite,React.lazyand loading ahead of time when hovering the link. Then it runsinit, so they come with the.npmrc, the theme for the--brandcolour and the provider, anddoctorhas nothing to say. It does not write into a folder that has something in it.npm run consumerscreates both with the CLI of the packed package and checks that they install, pass their types and their lint, build, navigate without reloading and fit within their JS limit (measured: 150 KB gzip in Next and 85 KB in Vite).- Blocks: seven complete screens that
npx amset-ui addcopies into the project (AMS-1786). Management dashboard (dashboard), customer list (customer-list), new customer (customer-form, with itscustomerSchemaschema to validate on the server as well), customer record (customer-detail), invoice issuing (invoice-form), settings (settings) and sign-in (sign-in). Built only with library components: they import@amset/uilike your project does and do not copy the code of any component, so they weigh nothing.npx amset-ui list --blockslists them;addwarns about what they need that your project does not have. In Storybook, under Blocks, with their states in light, dark and mobile, and translated into English.scripts/blocks.test.tschecks that they only import the library, that they do not write a single colour or size, and that the seven, once copied, compile in strict mode. useLocale()is exported: the provider's language ({ locale, direction }) to format a date or an amount of your own the same way as the library. The blocks needed it, as does any application that writes its own figures.TablewithonRowPress: the row also opens with Enter. The row could already be focused with the arrow keys, but it only responded to clicks: with the keyboard there was no way to open an invoice from the dashboard. Enter on a control inside the row still belongs to that control.
0.17.0, released October 19, 2026
0.15.0 and 0.16.0 were never published: this is the first published version since 0.14.0, so anyone coming from 0.14 gets everything in the three sections at once (0.15.0, 0.16.0 and this one).
- The AI components, configurable enough to put them into the assistant's chat. What the real AMSET chat does and the pieces did not allow you to configure:
PromptInputacceptsbusyModes(keep typing while it answers -add to the turn, stop and correct, queue-: real radios above the field, andonSubmitreceives the chosen mode in{ mode }; "Stop" now sits next to "Send"),inputRef,actionson the right,onPasteText(a huge text is attached as a file instead of being pasted),onInputKeyDownfor your own shortcuts,inputProps(spellcheck, language, maximum length) andattachDisabled.ChatMessagegainsactions(your own actions next to copy, regenerate and edit),actionsVisibility,editor(edit in the same bubble) andhighlighted(a search match, with a fixed ring and not an animation).ActivityLoggainsgroupper event (a history of several tasks, with a title per section and without merging identical items from two sections),renderTextandloading. And a new component,FindBar: the bar for searching within a long conversation, with a counter, next, previous and close (Enter, Shift+Enter, Escape). The second argument ofonSubmitis new but does not break anyone; 36 tests with real keyboard and focus. - The AI pieces match the assistant's real chat (AMS-1773). AMSET's chat was read piece by piece and what it did and the library did not was added:
PromptInputgainspastedFileName, a guard against the double-send bounce (the same text twice within 0.3 s),busyModeScope,history(up and down arrows, with the caret at the end) andsubmitOn="auto";Attachmentgainsicon,detail,onPreview,compactand the "Uploaded" announcement;ThumbnailgainsonPress,loading,errorandcaption; newImageViewer(the near-full-screen viewer, a real dialog) and the hooksuseBlobUrlanduseElapsed;ArtifactCardgainsonRetry;ModelSelector,maxFixes;Conversationgainsloading,loadingOlder,onReachStart(keeps the reading position when messages arrive at the top),stickToBottom,revealKey,highlightedandemptyas a node;ReasoningandActivityLogcount the time live (startedAt,endedAt,label,emptyIcon);Suggestions,allowPartial;UsageBadge,cachedTokens;FindBar,hasMore. Everything is optional: nobody who already uses them changes. Left out on purpose: the PDF preview (aniframewithoutsandboxwould open the door to running foreign HTML; a piece that only accepts PDF is missing) and markdown (no dependencies in the core). - Two pages no longer share a title (AMS-1783): the amount field goes from "Amount" to "Amount field" and the card field from "Card" to "Card field", like "Text field" or "Number field". The page ids do not change, and
scripts/docs.test.tsfails if two pages repeat a title again. - The coverage thresholds rise to what is measured (AMS-1763): from 80/80/75/80 to 95/94/86/92 (lines, functions, branches, statements). New tests for row dragging, the column width handle, the grid keyboard and the interaction states, which were the weakest areas (between 12% and 42%).
- Breaking Breaking: three root classes take the name of the component that actually uses them (AMS-814).
.amset-code(the one-time code field) becomes.amset-code-fieldwith all its children and modifiers,.amset-heading(the page header) becomes.amset-page-header, and.amset-list(the list in the dropdowns' panel) becomes.amset-listbox. The--amset-code-*and--amset-heading-*variables do not change. Anyone who styled those classes from outside has to use the new names. - The action menu's type-to-search uses the library's typeahead (AMS-1200):
useTypeaheadnow accepts a function to read the keys from the DOM and the menu no longer carries its own copy. It still finds "Éxito" with "e" and keeps the space as part of what is typed. - The tray bell and the open button of the search field and the multi-select use
amset-trigger-open(AMS-1293): the two copies of the "trigger with the panel open" rules are deleted. Flagno longer puts all 244 flags into the page's JavaScript (AMS-1893). The drawings are split by region (Europe, America, Africa, Asia, Oceania), each in a file that is requested when one of its flags is painted: whatFlagcosts goes from about 17.6 KB of compressed JS to about 1.5 KB. While the drawing arrives, the two letters are shown, and a server that waits for suspense (Next,renderToPipeableStream) already includes the flag in the HTML. NewpreloadFlags()to have it on a client's first paint. The package budget gains theFlagscenario withstaticOnly, which only counts what is downloaded through static imports.Table: the row header (rowHeader) is no longer centred (AMS-1884). The<th>inherited the browser's centring; it now aligns to the start and in bold like the rest of the cells, andalign="end"/"center"still take precedence.Pagination: asizethat is not in the list of sizes now shows in the selector (AMS-1883). Previously the selector came out empty or truncated; now the size is added to the options, without duplicates and sorted.RadarChart: the maximum mark no longer overlaps the name of the top axis (AMS-1895). It hangs below the outer outline.- The
RelativeTimehydration test no longer depends on the machine's time zone (AMS-1896): it fixes the defaulttimeZoneofIntl.DateTimeFormat, because changingprocess.env.TZdoes not work in Vitest threads. - All the code is in English: not a single variable, function, parameter, property or type in Spanish (AMS-1887). About 3,400 distinct names were renamed (more than 37,000 uses in 800 files) with the TypeScript LanguageService, not with text substitutions:
claveDeFila→rowKey,esClicDeEnrutador→isRouterClick,alPulsarFila→onPressRow,ordenable→sortable. What a consumer may notice:bin/proyecto.mjsbecomesbin/project.mjs;dist/fuentes/(whatamset-ui addreads) becomesdist/sources/; the ids of theamset-ui doctorfindings are in English (registry,layer-order,legacy-names,provider,router,project,installed,theme-not-imported); the status of each file inaddandinitisnew/equal/overridden/kept/modified;AppShellusescontentas the defaultidof the<main>; thedata-photoattribute ofAvatarisloading/loaded.amset-ui migratestill looks for the old names (written as strings inbin/migrations.mjs); its list hadinitialsValuesinstead ofvaloresInicialesand did not find that prop, now fixed. It is guarded byscripts/identifiers-english.test.ts, which walks the syntax tree of the whole repository. LocaleProviderno longer drags in the date picker (AMS-1885). It was defined insidedate-picker.tsx, so any application that mounted it -which is all of them, on all their pages- pulled in thedate.csssheet (4.6 KB gzip, measured with Vite) and, in a Next build with Turbopack, also the calendar, the editable segments and the whole picker: ~45 KB uncompressed on a home page that did not paint a single date. It moves to its own module,components/locale-provider.tsx. The API does not change:LocaleProvider,LocaleProviderPropsandDEFAULT_LOCALEcome from the same index and the same subpaths. Measured on amset.tech: the initial JavaScript drops by 10 KB gzip and the library's from 144 to 116 KB uncompressed. It is guarded bysrc/locale-provider-weight.test.ts(the provider's import graph cannot touch dates or floating layers) and a new weight budget scenario,LocaleProviderat 2.2 KB, which only counts what is static: the key catalogue that the provider requests withimport()in development is 20 KB that nobody downloads in production and would hide a change of a few KB.RichTextToolbarandToggleButton: the toolbar for a rich text editor, without putting the editor in the library (AMS-1787). The toolbar talks to the editor throughstate(what is applied where the cursor is) andonCommand, so it works with Tiptap, with Lexical or with any other, and@amset/uistill has no dependencies. It is aToolbar(one tab stop, arrow keys, focus memory) with aToggleButtonper format (aria-pressed, shortcut inaria-keyshortcutsand in the tooltip, Control or Command depending on the system), named groups, link and image with panels that are only mounted when opened and that are not a<form>(it works inside the application's form), and pressing with the mouse does not take focus away from the editor. Addresses are checked withnormalizeLinkHrefandnormalizeImageSrc, which are exported: an allowlist of schemes, ajavascript:or adata:does not get through. The image requires alternative text or being marked as decorative (alt=""). New sheetstoggle-button.cssandrich-text.css, with the editor frame (.amset-richtext__frame) and the typography of the written content (.amset-richtext__content). Guide in English indocs/rich-text-editor.mdwith the two complete recipes (on each editor's core, without@tiptap/reactor@lexical/react, which drag in@floating-ui), checked by the compiler and tied to the guide by a test, and a checklist for the manual pass with NVDA and VoiceOver of editableTable,Combobox,InvoiceLinesEditorand the toolbar itself. Tiptap and Lexical come in only as Storybook development dependencies, where two stories actually run them.WeekAgendano longer breaks hydration in English (AMS-1894). The week header comes fromformatRange("Oct 11 – 17, 2026"), and Node's ICU writes thin spaces (U+2009) around the dash where Chrome puts normal spaces: rendered on the server, the agenda did not match on hydration and React threw away the HTML of the whole page (error 418, measured on amset.tech, only in English because in Spanish that format has no spaces). The calendar, the date segments and the timeline already normalised it, each with its own copy; now all four go throughutils/plain-spaces.ts. It is pinned by a test inweek-agenda.test.tsxthat renders the agenda inen-US(verified to fail without the fix).- Decided:
onPressremains the press event, andisDateUnavailableandisLeafkeep their "is" (AMS-1762). First decision record in the repository, indocs/adr/(in English):onPressis the name that all the fronts already write and it unifies mouse, keyboard and finger in a single event;onClickremains only as a migration path in the button family and inTextButton, always alongsideonPressand with the nativeMouseEvent(AMS-735).isDateUnavailableis a predicate, not a state, andisLeafis the three-state marker for lazy loading. Not a single prop changes.prop-names.test.tsstops tolerating them silently: it lists them as exceptions with a link to the record and fails with any other "is" or with an ownonClickwithoutonPress. npx amset-ui migratealso covers what 0.16 moved to English (AMS-1894). It was written for what 0.14 removed and knew nothing about the 0.16 table; migrating amset.tech revealed the gaps. It now rewrites the 133 per-component sheets (@amset/ui/estilos/boton.css→@amset/ui/styles/button.css, also when imported individually),estilos.css,fichas.jsonandmandos.json,Tooltip para→trigger,PasswordField nueva→newPasswordanderrorDeImporte→amountError. The sheet table was matched by CONTENT, comparing the rules of each sheet in the two published versions: all 133 have a destination and none maps to the same one as another. Fixed along the way: the codemod only looked at theTableandFormprops written in the code, so a new entry inPROP_RENAMESwas ignored without warning; now the table rules. What still has to be done by hand: thelinea1/linea2keys of an address (the compiler already says "did you meanline1?") and thedata-tema/data-densidadattributes in the HTML, which may belong to the application itself.- Storybook: the control descriptions stop using old names and the size guide now shows the "Length" column (AMS-1667).
SlotPickersaidhorawhere the prop istime,WeeklyScheduleandRangeFiltersaid{ desde, to }where it is{ from, to }andclosedSchedule()appeared ashorarioCerrado(); and theSizeGuidestories declared thelargomeasurement but gave the values under thelongkey, so the column came out empty. Documentation and stories only: no API changes. BarChart: reference markers and a second figure per bar (AMS-1818).markersdraws lines that cross all the bars at their value (the group average, a target), in columns and in rows: they are included in the axis ceiling, appear in the table with their name and value and do not count in the chart summary; the layer measures the same as the area where the bars grow, so the line at 62% falls exactly where a bar of 62 would end (measured at 0 px).DataBar.detailputs a second visible figure after the bar's number ("+12 pts"), only in rows, where there is room; in columns it stays in the table, next to the note. New variables--amset-bars-nameand--amset-bars-row-gap. What is still pending from that ticket (stacked correct/blank/wrong bar and the per-point detail inLineChart) is new API and is handled separately.MonthAgenda: events can be pressed (AMS-1817).AgendaEventacceptshref(the event in the day's list is a link, which goes through theRouterProviderjust likeLink) andMonthAgendaacceptsonEventPress(key): withouthref, the event becomes a button that reports its key; withhref, it also reports when the link is followed. The card looks the same and the whole of it can be pressed (2 px focus, border in the event's colour on mouse hover). With neither of the two, the event remains text. What is still pending from that ticket (month grid with titles inside each day and agenda view by range) is new API and is handled separately.Table: the rows-per-page selector is no longer truncated, and therowHeadercolumn aligns to the start (AMS-1819). The selector measured a fixed 4.5 rem and with a wider figure it showed "5…": now that is a minimum and it grows with its value. Ath scope="row"is centred by the browser, so arowHeadercolumn ended up centred under a left-aligned header; the library now saystext-align: startfor allthcells that are notendorcenter. And the toolbar's free bar (.amset-table__bar-free) no longer shrinks without a floor until it truncates a custom toolbar at 390 px: it has a minimum and wraps onto a new line.PhoneField: the number fits with long prefixes in a narrow field (AMS-869). With +351 in anlgfield of 224 px the last digit overflowed by 7.4 px; the prefix trigger now takes 12 px less (less space between the flag, the prefix and the arrow: 97.2 → 85.2 px measured) and the number hasmin-width: 0.- Own page for 14 components that only appeared inside their group's page (AMS-1898).
AreaChart,StackedBarChart,Grid,Container,Center,ScrollArea,AspectRatio,Kbd,Mark,Highlight,Blockquote,ColorPanel,ToggleChipGroupandUploaderhad a public API and had no page, no index entry and no search by their name: now each one has its own in Storybook and indocs.json(202 pages), with its "Interactive" story, its English text and its keyboard and accessibility measured in a browser.Uploaderwas not described anywhere. The pages forStack,Code(now "Inline code"),LineChart,ColorPicker,ToggleChipandFileUploadstop describing the others and link to them. Along the way:Blockquotewithcitelost the attributes passed to it (they now go to the<figure>); inUploaderthe attributes,classNameandstylelanded on the inner area and not on the box, and when the last file was removed focus went to thebodyand the announcement was lost (it now returns to the choose button and is announced); the colour area handle was positioned relative to the popover and not the area (52 px off inColorPicker, outside the panel with a standaloneColorPanel), and now it lands where its value says; in the hue strip, End (or dragging to the edge) took the handle to the start because 360° is 0°, and it now ends at 359°;ToggleChipGroupandUploaderintroduce--amset-chip-group-gapand--amset-uploader-gap; and Storybook builds again, as it importedLocaleProviderfrom its old location. - Floating layers reposition when the typography loads (AMS-1176). A reflow that moves the trigger without changing its size (the web font that has just arrived) triggered neither scroll, nor resize, nor the trigger's observer, and the layer kept the old measurement until the first resize. It is now repositioned when
document.fonts.readyresolves and when the size of thebodychanges. - Hydrating a screen with
DatePickercosts a third: the closed panel no longer mounts its calendar (AMS-1886).FloatingDialog-the panel of the date picker, the range, the colour, the menus, theDropdownand the table filters- rendered its content even when closed, so eachDatePickermounted a whole month that nobody saw, and each day created itsIntlformatters (calendarOf, the long date, the day number). Now the content is mounted on open and unmounted when the exit finishes, and the formatters are created once per language (utils/intl-cache.ts). Measured with the apps benchmark (CPU ×4, median of 15): the orders screen hydrates in 150 ms instead of 264, on a par with Mantine (149) and ahead of MUI (242) and antd (352); the closed date picker goes from adding ~250 ms to ~40. Warning: anyone who looked in a test for something inside a closed panel will no longer find it until it is opened. - Breaking Breaking: ALL the API that is touched from outside moves to English. AMS-550 translated components and props, but functions, attributes, files, subpaths and the CLI remained in Spanish, and the examples in the reference still used the old API (
<Boton variante="solido">). What changes when upgrading:Before Now @amset/ui/estilos.css,@amset/ui/estilos/<hoja>.css@amset/ui/styles.css,@amset/ui/styles/<sheet>.css(the sheets, also in English:boton.css→button.css)@amset/ui/formulario,@amset/ui/esquemas@amset/ui/form,@amset/ui/schemasfichas.json,mandos.json,ejemplos.json,idiomas/*.jsondocs.json,knobs.json,examples.json,locales/*.json(and their keys:component,module,title,family,description,stories,name,knobs,story,code)npx amset-ui tema,npx amset-ui mandos,npx amset-ui migrar,amset-tema.cssnpx amset-ui theme,npx amset-ui knobs,npx amset-ui migrate,amset-theme.css;addwritesstyles/andstyles.cssdata-tema,data-densidaddata-theme,data-densitydata-cargando,data-seleccionada,data-deshabilitada(Card) and the rest of thedata-*in Spanishdata-loading,data-selected,data-disabled… (data-actual→data-current,data-sin-leer→data-unread,data-nivel→data-level…)@layer amset-ui.componentes@layer amset-ui.componentsClasses amset-no-imprimir,amset-only-impresion,amset-for-imprimir,amset-card-pago*,amset-motion-esencial,amset-order-state__hitoamset-print-hidden,amset-print-only,amset-printable,amset-payment-card*,amset-motion-essential,amset-order-state__milestone(y--amset-order-state-milestone-gap)Animations amset-*-entra,-entrar,-salir,-aparece,amset-progress-va-y-viene,amset-rating-marcar-enter,-exit,-appear,amset-progress-back-and-forth,amset-rating-markcsvDeTabla,descargarCsv,separadorPorDefecto,filtrarFilas,ordenarFilas,paginarFilas,opcionesDeColumna,valorDeCeldatableToCsv,downloadCsv,defaultSeparator,filterRows,sortRows,paginateRows,columnOptions,cellValuecalcularFactura,errorDeImportecalculateInvoice,amountError; andrejectionReason/FileLimitsfromFileUpload, which the README presented as exported and were notTooltip para,AddressFieldlinea1/linea2,parseHotkey(...).teclaTooltip trigger,line1/line2,.keyPasswordField nuevanewPasswordAvatar state="inline"(it was "en línea" mistranslated)state="online"TaxZone:peninsula,baleares,canariasmainland,balearic-islands,canary-islands(ceutaandmelillastay)TransferListmoveText(..., 'elegidos' | 'disponibles'),ROOT_FIELD = 'raiz''chosen' | 'available','root'Text keys CabeceraDeAgenda.*,FilaDeFranja.*,TablaDeDias.*,NumerosDePagina.*,TiraDeDias.*,HorasDelDia.*AgendaHeader.*,SlotRow.*,DayTable.*,PageNumbers.*,DayStrip.*,DayHours.*Storybook ids componente-boton--jugar,componentes-*,sistema-*,fundamentos-*component-button--playground,review-*,system-*,foundations-*; the story exports also in English (Cargando→Loading) and the visible name in SpanishThe repository's files and folders also move to English (
src/components,src/utils,src/styles,src/form,src/schemas,src/table,src/messages/texts,boton.tsx→button.tsx,DISENO.md→DESIGN.md...). What is noticeable from outside:npx amset-ui addcopies tocomponents/,utils/,messages/texts/andstyles/instead of the previous names;DESIGN.mdandACCESSIBILITY.mdship in the package with their new name; and the files indist/hang from the same folders (dist/components/button.js), although from outside they can only be reached through the index and theexportssubpaths.The
package.jsonscripts too:build,build:css,build:css-components,build:docs,build:examples,build:sources,build:locales,build:storybook,typecheck,verify,test:watch,e2e:engines,e2e:engines:gate,consumers,budget,auditandexport:figma(previouslyconstruir*,tipos,verificar,test:mirar,e2e:motores*,consumidores,presupuesto,auditarandexportar:figma). Anyone with annpm run construirin a branch's CI should change it.The parameters of the exported functions are also named in English, and
scripts/docs-api.test.tschecks every example in README, REFERENCE and Getting started with the compiler: imports, components, props and values. - Storybook without accessibility noise and with the code in English. axe was run over the 1,036 pages (stories and documentation, light and dark): the PAGE rules (
region,landmark-one-main,page-has-heading-one) fired on almost every story by construction and are turned off in the bench (src/bench/a11y.ts); the heading-order and repeated-landmark rules are turned off only on the review and system pages, which stack loose pieces. What did belong to the component: the header of the calendar and the agenda was a<header>that axe counted asbanner, it is now a<div>; the empty column ofBoardand an emptySortableListhad no valid row inside thegrid(aria-required-children); the accessible name of eachBoardcolumn had "tarjetas" written in Spanish and now comes from the text bundle (Board.column, with plural, also in English); a disabledRangeFiltercarriesaria-disabledand its dimmed parts are no longer measured for contrast; and the neutral bar ofTimelinedid not reach 4.5:1 in the dark theme. Separately, 504 stories had Spanish names (Cargando,ConError,SoloLectura…): now the export and the Storybook id are in English, the visible name stays in Spanish, and the identifiers of the stories, the bench and.storybook/too (fotoDeMuestra→samplePhoto). - The side panel and the dialog once again stop the page behind them and say on their trigger whether they are open (AMS-1284). When both moved to
<dialog>, two things that React Aria did were lost without a single error: the page behind kept scrolling with the modal open (measured in the bench: the wheel over the backdrop took it from 200 to 1,000 px) and the trigger stopped carryingaria-expandedandaria-controls, so a screen reader did not say "expanded" and it did not stay pressed with its layer open. Now, while aDialog, aDraweror aCommandPaletteis open, the root hasoverflow: hiddenand, if the scrollbar takes up space,scrollbar-gutter: stable, so that nothing jumps sideways; whatever the page had inline is restored on close, and a dialog opened from a panel does not release the page until the panel is closed as well. The trigger passed throughtriggerreceivesaria-expanded,aria-controls(only with the layer open, pointing at theidof the<dialog>, yours if you give it one) andamset-trigger-open. It costs 0.34 KB compressed in each; the weight budget adds aButton, Drawerscenario. benchmark/: MUI and Mantine in the individual pieces, and three equivalent apps measured in a real browser (AMS-1784). The pieces (Button;Button + Select + DatePicker;Table) now compare seven libraries with pinned versions; Mantine with the minimal CSS, only the sheets for what remains in the bundle, checked at build time. In addition, the same screen (form with validation, table with sorting and pagination, confirmation dialog) built with @amset/ui, MUI, Mantine and antd, rendered on the server and hydrated in Chromium: it measures what the page downloads, the JS that is parsed and the hydration, and each app goes through the same walkthrough by roles and axe without failures.npm run benchmarkbuilds, measures and rewritesbenchmark/RESULTADOS.mdwith the versions, the date and the caveats. Measured today: the screen loads 127 KB gzip with @amset/ui, 176 with Mantine, 220 with MUI and 391 with antd.- The button keeps focus when pressed with the mouse in Safari as well (AMS-1881). Safari does not focus a native
<button>when it is pressed, neither with the mouse nor with a finger, so when opening aDialog, aDraweror a menu with a click and closing it, the browser had nowhere to return focus to and it fell on thebody(anyone continuing with the keyboard went back to the start of the page).ButtonandIconButtonnow take focus onclick-not onmousedown, where Safari undoes it and Firefox shows the focus ring with the mouse- and beforeonPress, which is when the layer checks who has focus. Nothing changes with the keyboard. It costs 70 bytes compressed.
0.16.0, released October 18, 2026
npx amset-ui migrar: rewrites the code that uses the names that 0.14 removed (AMS-1831). TheTableprops and column keys (enServidor,paginacion,ordenable,numerica...), the sort'ssentido,Formulariowith its props (esquema,onEnviar...), the*Formfields,BotonEnviar,resolverDeEsquema,PropsCalendarRange,TableSortInputand the call toariaGranularity. It does so with the syntax tree from the project's own TypeScript, not with regular expressions: it tells a prop apart from the same word in a comment, a string or another component, respects your indentation and your comments, follows imports with aliases and with namespaces, and columns declared in a variable. What it cannot decide on its own (a...props, a.sentidoread in a handler,PropsForm,ERROR_NETWORK_COUPON) comes out as a warning with file and line.--dry-runcounts beforehand,--checkexits with 1 if anything remains (for CI), and it is idempotent.doctorwarns (error) when those names remain. Covered by 19 tests, including one that compiles the old code (fails) and the migrated code (compiles) against the library's real types, and run dry against four in-house fronts without a single false positive.
0.15.0, released October 17, 2026
InvoiceLinesEditor: the row no longer overflows its box (AMS-1812). With the withholding column (the default) the single-line row adds up to about 65 rem of columns, but the threshold for switching from card to row was 52 rem, the same with and without withholding: between 832 and 1040 px of container it overflowed by up to 210 px and the amount and the remove button were left outside, clipped (without withholding, 38 px at 832). There are now two thresholds, 55 rem without withholding and 66 with it; below them, each line is the two-column card.e2e/factura-ancho.spec.tssweeps twelve widths and fails if the row overflows (verified that it bites). It was uncovered by an axe sweep over all the Storybook stories, which counted the clipped amount as a 1.12:1 contrast.- Breaking BREAKING (fixes a bug):
NumberField,QuantityPickerandMoneyFieldsubmit the number in a form, not the text that is shown. Thenamewas on the visible field, which holds the number in the language's format: in a native form,10,5arrived as"10,5"(a server reads it asNaNor as10),12.000as"12.000"(a12) and an amount as"1.234,56 €". Now thenamegoes on an<input type="hidden">with the canonical number —10.5,0.21,12000,1234.56—, always with a point, without thousands separator or symbol, already clamped to minimum, maximum and step as the field leaves it on blur, and empty if there is no figure or it cannot be understood. A disabled field is not submitted, like an<input disabled>. Warning: the visible field no longer has aname; if a test or a script looked it up by[name=...], it now finds the hidden one. NewcanonicalNumber()inutilidades/numero. 12 new tests (numero-formulario.test.tsx); 9 fail against the previous code. - Equivalence surcharge on invoices (AMS-1785).
calcularFacturaandInvoiceLinesEditoradd the surcharge to the VAT according to its rate -5.2% with 21%, 1.4% with 10%, 0.5% with 4%, art. 161 of Law 37/1992- with its own row in the breakdown (surchargeBreakdown) and in the total. It is requested for the whole invoice (equivalenceSurcharge) or line by line (surchargePerLineadds a checkbox per line and the line's one takes precedence). It is rounded once per rate, like the VAT, not line by line (ten lines of 0.10 give 0.05 and not 0.10).surchargeRateschanges the rates; the transitional 0.62% for 5% is not included by default. Without requesting it, the usual totals do not change. Exported from the index:SPANISH_SURCHARGE_RATESand the new types. Item,ItemGroup,InputGroupandRadarChart. The generic row (withhreforonPressthe TITLE is the link or the button and its area is stretched with CSS to cover the row: a single interactive element, and the actions on top as separate buttons); the field with segments joined inside a single box, with the focus ring, the help text and the error ofTextField; and the radar chart (scale from zero, anullis a gap and not a zero, a summary for screen readers with the highest and lowest of each series, and the whole table).radarPoint,ringPathandseriesPathcome fromutilidades/grafico-radar.ts.TextButton, the text action (AMS-1792). A<button>with the look and tones ofLinkfor an action that sits inside a sentence (retry, dismiss): the screen reader says "button", it responds to Enter and Space, it istype="button"by default and it does not submit a form twice whileloading. Without the box or padding ofButton: it inherits size and line height, and on touch the tap area grows to 44 px with a pseudo-element, without moving the line (a<button>is always inline-block: padding would stretch it). It replaces the hand-written button that every front built for this.HoverCard,MenubarandNavigationMenu. The card that appears when hovering over a link (its content can be interacted with:closeDelaylets you reach it, Tab enters because it comes right after and Escape dismisses it, WCAG 1.4.13); the desktop menu bar (a single tab stop, left and right arrows with wrap-around and reversed in RTL, and with a menu open the mouse opens the neighbouring one, which a modalActionMenucannot do); and the main navigation with ARIA disclosures (real links, the panel right after its button,aria-current, Escape and tabbing out close it, and pressing a button that the mouse has just opened PINS it instead of closing it).useMenuListis extracted fromMenuPanelto share the list keyboard with the menu bar.- The phone prefix appears in full in the list (AMS-1703). Dominica, the Bahamas, Jamaica and the rest of +1 all showed as "+1"; now the list and the accessible name say "Dominica (+1 767)", and with several ranges "+1 809 · 829 · 849" in the accessible name and the title, and "+1 809…" in the row (the full one split the country name over two lines: 43 px for the Dominican Republic row versus 36). The trigger stays at "+1" (the area code digits are already in the typed number, and repeating them would give "+1 767 767 555…"); the United States, Canada (too many ranges) and area codes of one or two digits stay at the dialling code. The E.164 does not change.
dialLabel()is exported from the index. ModelSelectorandArtifactCard(+ArtifactFrame), the two that were missing to migrate the assistant's chat. The engine and model selector is purely presentational (the catalogue and the choice are supplied by whoever uses it): two rows of native radios —the arrow keys skip disabled engines—, a disabled engine states its reason in text, and if the selected one has been disabled the button is flagged and the panel warns;fixLabel+onFixadd the link to fix it. The artifact card shows the thumbnail or the icon according to the type, open and download as SIBLING buttons of the header, and the preview is only mounted when open;onVisiblerequests the content of what comes into view.ArtifactFrameisolates the HTML withsandbox="allow-scripts"and neverallow-same-origin, and it cannot be removed from outside.selectionLabelandartifactKindare exported separately.npx amset-ui initandnpx amset-ui doctor(AMS-1778).initleaves a project with the library set up properly: the@amset:registryline in.npmrc(never a token),@amset/ui/estilos/base.cssat the top of the global CSS —which fixes the layer order before Tailwind 4 without paying for the CSS twice—, your theme generated with thetemacommand (contrast checked),AmsetProviderand the Next router mounted in the layout,_appor<App />. It is idempotent, it does not overwrite a file of yours (it only adds lines to it),--dry-runreports what it would do and--installinstalls with your package manager.doctordiagnoses a project that already uses it and exits with code 1 if there are errors: Tailwind 3 without layer order,data-temawith other values, a token as${VARIABLE}in.npmrc, Pages Router withouttranspilePackages, and warnings for an ungenerated theme, links that reload the page, Jest without a CSS mapping. Tested with unconfigured Next and Vite projects that go throughinit, build, open in Chromium and carry the requested brand colour (npm run consumidores,scripts/init-doctor.test.ts).refworks with React 18 (AMS-1782). In React 18 a function component does not receiverefas a prop and discards it:<Button ref={nodo}>left it asnullandDropdowndid not open its panel from aButton(measured with real React 18).Button,IconButtonandFloatingButton-the only three that exposerefin their public type- are now wrapped withwithRef, aforwardRefthat passes therefto the render; in React 19 everything stays the same. Newnpm run test:react18(and its CI step): installsreact@18separately and renders every page and the buttons withrefwithout a single exception or React warning, so "React 18 and 19" stops being an unchecked promise.
0.14.0, released October 17, 2026
- Breaking Breaking: the old names that remained as aliases are gone (AMS-1761). A deprecated alias is dead code: out go those of
Table(enServidor,seleccionadas,alSeleccionar,paginacion,columnasConfigurables,alCambiarColumnas,redimensionables,alPulsarFila,barraSuperior; in the columnnumerica,ordenable,filtrable,ocultaPorDefecto,pegada,cabeceraDeFila; andsentidoin the sort, with its typeTableSortInput), the Spanish names of the form (Formulario,CampoTextoForm,CampoAreaForm,SelectorForm,SelectorFechaForm,CasillaForm,InterruptorForm,GrupoRadioForm,BuscadorForm,BotonEnviar,resolverDeEsquema,PropsForm,EsquemaEstandar,IncidenciaEstandar,ShippingAnswer),PropsCalendarRange,ariaGranularityandERROR_NETWORK_COUPON. The table of equivalents is in the README. amset-ui-web and amset-assistant move to this version at the same time. - The
DatePickercalendar no longer reappears when it closes (AMS-1766). Closing the layers waited for ALL the animations and transitions of the panel, including those set by the CSS of whoever uses the library: in amset-assistant, a 200 ms transition onscrollbar-colormeant that the 100 ms exit finished, the panel became fully visible again for about 80 ms and only then closed. Now it waits for@keyframesanimations and for the transitions that move the layer (opacity,transform,translate,scale,display,overlay), and the exit of the floating layers and of theTooltipstays on its last frame (forwards) untilclose(). - README in English (AMS-1716).
README.mdbecomes the English front page —what npm and GitLab show—: installation, usage, styles, themes, Next.js, on-demand languages, forms, measured weight, tests, tools for AI assistants and licence. The full per-component reference remains in Spanish inREADME.es.md, which now ships in the package.src/readme-ingles.test.tsties the front page to reality: everynpm runand every@amset/ui/...subpath it cites has to exist, and the documentation test also checksREADME.es.md. - Reference in English (AMS-1774).
REFERENCE.mdtranslates the full per-component reference (README.es.md, 2,674 lines) into English and ships in the package, linked from the front page.src/referencia-ingles.test.tsprevents them from drifting apart: same headings in the same order, same code blocks and same tables, and the same--amset-*variables and components cited in both. - Components for AI interfaces:
PromptInput,ChatMessage,Reasoning,ToolCall,ActivityLog,Attachment,SuggestionsandUsageBadge. They come from what the AMSET assistant's chat already did, without losing anything: the box grows with the text up to a limit, sends with Enter and adds a new line with Shift+Enter (without sending while composing with an IME), recalls the last message with the up arrow, accepts pasted, dragged or chosen files and switches from "Send" to "Stop" while it answers; the log follows the end only if it was already at the bottom (useStickToBottom, exported) and groups repeated events; a tool call that fails opens by itself. Everything is conveyed with text as well as with colour, with no dependencies and with one CSS file per component.formatDuration,formatBytes,collapseRunsandisNearBottomare exported separately. A new@keyframes,amset-caret-blink, shared by the three cursors. FormTreeSelect, the connected field that was missing (AMS-1675).@amset/ui/formularionow has a connected field for every field with a value:FormTreeSelectstores the key of the chosen node, or the list of keys withselection="multiple"(in tree order, not click order), and shows the schema error in the field.FormCascaderandFormTreeSelectare also added to the reference's tables of connected fields, from which the former was missing.- CSS order no longer decides how a field looks (AMS-1775). The box and the input of all fields (
.amset-field__controland.amset-field__entry) had the specificity of one class, the same as their variants (the text area, the number field, the search field...), and whichever the bundler left last won: in the library on its own the order is fixed byindex.css, but in a consumer it is not. Measured in a browser with the sheets in the wrong order: theTextAreacame out with a 36 px box around a 56 px textarea (in Next 16 with Turbopack, 46 around 67), and with line height atnormalinstead of 21 px. Now both bases go in:where()and have zero specificity: any variant, and any CSS from whoever uses the library, always beats them. In both orders the box measures the same (80.3 px) and does not overflow. A new test (orden-del-css.test.ts) prevents the base from being written with a bare class again. No API changes. - Firefox and WebKit in the e2e, and what they found (AMS-1716).
npm run e2e:motoresruns the behaviour e2e in both engines (opt-in withAMSET_MOTORES=1, without axe or screenshots), and CI has a fast gate,e2e:motores. Two real bugs that Chromium did not show: in Safari, when closing a dropdown or a confirmation, focus was lost on the<body>(Safari does not give focus to a button when it is clicked with the mouse, and the browser returns focus to "whatever had it"); now the layer returns it to its trigger if it has been lost. And in Firefox and Safari the dialog, the side panel and the command palette disappeared abruptly when closing (only Chromium animatesdisplayandoverlay); now the exit goes throughdata-exitingandclose()waits for it to finish, the same in all three engines. No API changes. - The brand now supports a second colour:
secondary. Until now onlybrand(the accent) existed, so a design with two brand colours could not be expressed. New family of tokens--amset-secondary,-hover,-active,-text,-softand-ink(violet by default, in light and dark, with their AA contrast pairs measured), keypalette.secondaryinamset-ui.config.js, andtone="secondary"on everything that already acceptedaccent(buttons, tags, alerts, cards, avatars, charts, text, links…). No changes for anyone who does not use it. - And a third one:
tertiary. Same recipe assecondary:--amset-tertiary*tokens (teal by default, light and dark, AA contrast measured),palette.tertiaryandtone="tertiary"on everything that acceptedaccent. - The second and third colours reach ALL components with
data-tone. Checkboxes, switches, sliders, tabs, steps, the selected day in the calendar, the focus ring… always paintedaccentand had notone. Nowdata-tone="secondary"ordata-tone="tertiary"on the component itself or on any container repaints everything inside (it remaps--amset-accent*and--amset-focus), without touching each component. - The documentation covers the three brand colours. Foundations → Colour lists the
secondaryandtertiaryfamilies (53 roles) and introduces "The three brand colours", a live demo oftoneanddata-tone; Getting started explains the newpalettekeys and the pages that spoke of "six tones" now name all eight. npx amset-ui add: copy a component's code into your project (AMS-1771). For anyone who does not want to depend on a published version:add button iban-field(or--all) copies the code with everything it needs, its stylesheets and a text catalogue generated only with what was copied;listshows the 190 available. It removes the.jsfrom imports (--keep-jskeeps it), does not overwrite a file you had already changed (--overwritedoes) and records what was copied inamset-ui.json. The package publishes the code indist/sources/(npm run construir:fuentes). Measured: the copied code compiles in strict mode and so doesadd --all.@amset/ui/esquemas: the validations of the Spanish fields, on their own (AMS-1771).documentSchema,ibanSchema,cnaeSchema,socialSecuritySchema,plateSchema,cardNumberSchemaandpostalCodeSchema: the letter of the tax ID (NIF), the IBAN mod 97, the CNAE division... as Standard Schema schemas that also includecheckandmessageto hook them into Zod or Valibot without the library depending on either. Same messages as the field, inesanden. Server subpath: no'use client'.ExportButtonandexportElement: charts and tables to PNG, SVG and PDF (AMS-1771). In the browser and without libraries: the element is copied with its already computed styles, placed into an SVG withforeignObjectand the three formats come from there (elementToPng,elementToSvg,elementToPdf,downloadBlob).scaleup to 4 for printing; it covers what overflows the box; it embeds fonts and images. The PDF contains the image, not text. A hidden element fails withExportError. Measured in Chromium withLineChart,BarChartandDonutChart. New textsExportButton.*.Tablewith 100,000 rows, measured (AMS-1771). WithvirtualizedandmaxHeight: first paint in 121 ms with 16 rows in the DOM, 61 frames per second when scrolling and 42 ms to sort. No code changes: it is only documented with the measurement.ACCESIBILIDAD.md: what is measured (axe with WCAG 2.0, 2.1 and 2.2 AA), what is checked separately and what has not been measured yet, without claiming a level that has not been verified (AMS-1771).
0.12.0, released October 16, 2026
- The library is tested in real projects (AMS-1716).
npm run consumidorespacks the package, installs it in a Next project (App Router) and in a Vite one, builds them, serves them and opens them in Chromium: typing of a third-party project, translated server HTML, hydration without errors, interaction, lazy languages that are not downloaded unless someone requests them and a first-load JS limit.consumidoresjob in CI. No changes for anyone using the library. - The developer warnings no longer say or load anything in production (AMS-1716). With
process.env.NODE_ENVbehind atypeof processguard, in the browser production was taken for development: withcatalogsthe provider downloaded the library's entire catalogue (18 KB gzip, measured in a real Vite app) to check the keys, and the table's deprecated-prop warnings appeared in the console. NowisDevelopment()decides, which leaves the expression that the bundler replaces without a guard. - Choosing a day in a
DatePicker(or any layer) inside aDialogno longer closes the modal. Thecloseandcancelof the child<dialog>bubbled up the React tree to theonClose/onCancelof the containing modal and closed it (also with Escape inside the calendar).Dialog,Drawer,CommandPaletteandFloatingDialogonly handle the close that is their own. - Text on a coloured fill is light in both themes (AMS-1737). In the dark theme the solid fills (
accent,error-solid,success-solid,warning-solid,info-solid) were light and carried almost black text: with a red or green brand you got black lettering on red or green. Now the fill has the same dark lightness in both themes and its ink (*-ink) is almost white, tinted with the family's hue, not pure white or black. Measured: with any brand hue and chroma the ink reaches 4.5:1 on its fill in light and dark (new test inpaleta.test.ts).highlightbecomes black in dark as well, so hovering always darkens. Warning: in dark mode the primary and status buttons look deeper than before; if you forcedcolorswith a light fill, the generator will refuse to write the theme. - The text of an active
ToggleChipis readable with any colour (AMS-1737). It used 75% of the chip's colour and the rest from the theme's ink: an intense pink in light or the accent in dark fell below 4.5:1. It now uses 30% (--amset-chip-ink-share), and a test sweeps the colour space in both themes. Markstops painting dark text on black. It used thehighlightrole (the direction in which a fill darkens) as its background; it now sits ongold-softwith the main ink, and the pair is in the contrast tests.- The completed part inside a timeline bar darkens the bar instead of lightening it, so that the dates remain readable on it.
- Block components fill the width of their slot without being asked. Fields and
Select, table, accordion, cards, alerts, tabs, pagination, charts, lists and the rest of the block components haveinline-size: 100%frombase.css: placed in a flex row they shrank to their content (on the website, 80 components; theSelectchanged width when an option was chosen). Those that size to their content (buttons, tags, avatars, switches,SegmentedGroup, text) do not change, and a test forces every new component to be classified. Inside other components, the phone prefix, the hours of the weekly schedule and the pagination's rows selector stay compact. - A standalone
Calendaris measured against its box and is centred: the days shrink with the slot instead of with the window (in a 238 px card the month measured 274 and overflowed) and the calendar fills its slot up to the size of the month. Gridequalises the height of what goes in each cell (equalHeight,trueby default): a card inside a wrapper fills the row just as if it were a direct child.Linkis underlined by default (underline="always"): inside a paragraph colour is not enough (axelink-in-text-block, 1.12:1 measured).underline="none"removes it for menus and bars.SegmentedGroup: the fill no longer flies off after scrolling the page. The starting point was measured when the previous option was chosen; with the page scrolled in between it started thousands of pixels away and the frame showed the scrollbar.benchmark/: weight compared with react-aria-components, shadcn/ui, HeroUI and antd (P1.6).npm run benchmarkbundles the same thing in each one with Vite (Button;Button,SelectandDatePicker;Table) and counts JS and CSS in gzip, with the versions pinned and the caveats written inbenchmark/README.md.- Requesting one component no longer brings the texts of all of them (AMS-1718). The entire
es/encatalogue (65 KB uncompressed) travelled with any component that said anything: aButtonweighed 23.4 KB gzip and now 5.9;Tablegoes from 54 to 38 KB and the week agenda from 35 to 18. Each component brings only its own bundle (src/mensajes/textos/);ESandENare still the entire catalogue.translatorFor(locale, paquetes, mensajes?)anduseMessages(paquetes)now receive the component's bundles, andcatalogForis replaced bycatalogEntry. A text requested from a bundle that does not contain it is reported in the console and makes the tests fail. - Security:
LinkandButtonLinkwithtarget="_blank"carryrel="noopener noreferrer"(AMS-1716). Previously they had noreland the destination page received the originating window and theRefererheader. Therelpassed by the user is respected and completed. The tracking address ofOrderStatusalso carriesnoopener. SECURITY.md, audit and weight budget in CI (AMS-1716).SECURITY.mdwith how to report and what the library does and does not do;npm run auditar(high and critical issues in what ships) andnpm run presupuesto(gzip weight of what is imported, measured with Vite) in theverificarjob;src/seguridad-urls.test.tsxensures thatjavascript:stays blocked.- axe also runs
wcag22aa(AMS-1716): in axe-core 4.13 it is thetarget-sizerule; 568 cases without violations. - All prop descriptions, in English too (AMS-1687). The 300 that were missing, from old components and new ones, now have their version in
src/mensajes/props.en.json; a new test inscripts/api.test.tsfails if a new prop is left with its description in Spanish only. LineChart,AreaChartandStackedBarChart(AMS-1698). Multi-series charts with gaps (null), an axis with round numbers, legend, values on hover, summary and table for screen readers. Sheetseries.css, textsSeriesChart.*.- The
Selectpanel is capped at 20 rem. With many options the panel could take up the whole window, with an extremely long scrollbar. It now measures at most 20 rem (about eight and a half options) and the rest is reached by scrolling; it can be changed with--amset-select-panel-max-height. Any floating layer can request its own cap with--amset-floating-cap; if the window is shorter, the panel keeps shrinking. WorkCalendarView,createWorkCalendarand holiday readers (AMS-1700). Working calendar without built-in holidays: they are loaded from the official source withparseIcsHolidays/parseCsvHolidays. Summary of working days per month,isDateUnavailablefor the date pickers. Sheetcalendario-laboral.css, textsWorkCalendar.*.WeekAgendaon mobile: on a narrow screen it switches to the list of days with the week strip at the top, like Teams (layoutauto|full|compact). On touch devices appointments are tapped and not dragged.WeekAgenda(AMS-1700). Week and day agenda with appointments that can be dragged and stretched (pointer and keyboard), layout of overlapping ones, a "now" line and announcements to screen readers. Its own sheet,agenda-semana.css; textsWeekAgenda.*in Spanish and English;placeBlocks,moveBlockandresizeBlockare exported separately.- Breaking BREAKING: a single name for each prop, the HTML one and without "is" (P0.1, AMS-1727). The library had two dialects: 44 controls said
isDisabled,isReadOnly,isRequired,isInvalidandisSelectedand 24 piecesdisabled,readOnly,selectedandopen, so anyone building a form had to remember which one went with each. Now they aredisabled,readOnly,required,invalid,selected,open,indeterminate,dismissableandkeyboardDismissDisabledon all of them, and no alias of the old names remains: they have to be changed when updating (in JSX,isDisabledbecomesdisabledand so on for each one).MultiSelectkeepsdisabled,readOnlyandrequiredlike all the others, and its "is" aliases disappear.isDateUnavailable(it is a function, not a state),onPressand the nativeonClickremain, anduseDisclosurestill returnsisOpenalongside its verbsopen()andclose(). A test (nombres-de-props.test.ts) prevents a name with "is" from coming back. InvoiceLinesEditor(AMS-1700). Editor for the lines of an invoice with VAT and personal income tax (IRPF) withholding per line and the breakdown of totals;calcularFacturaandlineAmountare exported separately. Its own sheet,factura.css. TextsInvoiceLinesEditor.*in Spanish and English.- Advanced
Table(AMS-1699). Grouping by column, filters with several conditions, CSV export, columns saved between visits, in-cell editing and row virtualisation. All optional: without the new props the table behaves as before. New textsTable.*in Spanish and English;csvDeTablaanddescargarCsvare exported separately. ContextMenuand submenus inActionMenu(AMS-1698). The right-click menu, with keyboard support, andMenuAction.childrenfor nested lists.CodeField: filling or clearing a box in the middle no longer shifts the digits on the right (AMS-1747). In controlled mode the gap did not survive the parent returning the value without gaps, and typing in box 5 with "123" entered left the 7 in box 4. Now the boxes keep their gaps as long as the parent returns whatonChangeemitted, andonChange/onCompletealways deliver the code without gaps (previouslyonCompletecould arrive with a space inside). Typing over a box that already has a digit replaces it instead of spreading the two, and an empty box is no longer painted with a space.- Firefox and WebKit in the e2e, and what they found (AMS-1716).
npm run e2e:motoresruns the behaviour e2e in both engines (opt-in withAMSET_MOTORES=1, without axe or screenshots), and CI has a fast gate,e2e:motores. Two real bugs that Chromium did not show: in Safari, when closing a dropdown or a confirmation, focus was lost on the<body>(Safari does not give focus to a button when it is clicked with the mouse, and the browser returns focus to "whatever had it"); now the layer returns it to its trigger if it has been lost. And in Firefox and Safari the dialog, the side panel and the command palette disappeared abruptly when closing (only Chromium animatesdisplayandoverlay); now the exit goes throughdata-exitingandclose()waits for it to finish, the same in all three engines. No API changes. - Fields without a box:
variant="bare"and--amset-field-touch-height. A field that sits inside another box (a chat bar, a search field inside a toolbar) had to remove its own with custom CSS, and when removing it it also lost the padding and the height, so the text ended up stuck to the edge and the field was taller or shorter than the buttons next to it.bareonTextField,TextAreaand the rest of the fields paints no border, background or underline, keeps the height and padding of the size and marks focus with a ring (and the error with the bottom line). And--amset-field-touch-heightsets the height of the box on touch (48 px by default, as always) for the front that uses shorter controls on mobile. Additive.
0.11.0, released October 16, 2026
- Links prefetch their destination (like
next/link).RouterProvideracceptsprefetch(Next'srouter.prefetch):LinkandButtonLinkcall it once per destination on mouse hover or when receiving focus. They do not do so with an external destination, with their owntargetor when disabled. Optional: withoutprefetchnothing changes. Next without wiring anything:@amset/ui/nextexportsNextRouterProvider(App Router) and@amset/ui/next/pagesexportsNextPagesRouterProvider(Pages Router); they are placed once at the top of the tree and all links navigate and prefetch through the Next router.nextis an optional peer: anyone who does not import those subpaths does not need it. New Routing page in Storybook (and, with it, on the website) that explains the generic provider and the two for Next. PageHeader,ButtonGroupandComboboxno longer carry'use client'. They use no state, effects or handlers of their own: in Next they render as server components and their code does not ship to the browser. Measured in a Next 16 app with a server page that uses them:PageHeaderandButtonGroupdisappear from the client JS (-1.4 kB, -469 B gzip). The rest of the components with the directive do use hooks, context or handlers.- Texts that the library was still writing by hand in Spanish (AMS-1720). With the language set to English they came out in Spanish: the instalment calculator ("al mes durante", "La última…", the minimum and "TIN / TAE"), the shipping selector ("Te faltan… para que salga gratis"), the board's announcement when moving a card, the "de 5" in the rating, the guided tour's counter, the follow-up question of the NPS survey (its default function was called without a translator), the address and card errors and the colour and channel names of the colour picker. They now come from the message catalogue.
addressErrors,postalCodeError,cardNumberError,expiryError,cvvError,cardErrors,moveAnnouncementandcolorNameaccept the translator as an optional last argument and, without it, return the same as before; newchannelName. The catalogue test no longer settles for looking for accents: it walks the JSX and the templates of each component. - Storybook: the demos, the documentation and the controls, truly in the chosen language (AMS-1720).
- The texts in the demos (buttons, tables, alerts, amounts) come out in English: the args reach the component already translated and the rest is changed in the preview with
src/banco/demos.en.json(3,400 texts; proper names and codes stay as they are). The amounts and dates that the stories formatted with a fixed'es-ES'usesrc/banco/formatos.ts. In Spanish everything stays as before, including what the reference screenshots capture. - Twenty-five Review and System stories wrapped the demo in a
LocaleProviderwithout a language that forced Spanish: removed. - The pages whose stories run in their own frame (
inline: false) came out in Spanish: each frame receives the language and the page reads it from its own state. - "Jugar" is renamed "Interactivo" (Playground in English). The ids do not change.
- Clicking a link in a demo no longer navigates the whole window (Storybook sets
<base target="_parent">): an anchor scrolls, an external one opens another tab. - The controls that the component only reads on mount (
default…,initial…, the story's state) now work: the demo is remounted when they change, except in Layers, where changing the title of an open dialog does not close it.currencyandlocaleare chosen from a list: typed by hand they crashed the story. - The documentation of 198 files used the API names from before AMS-550 (
alCambiar,valorInicial,plana,fantasma,clave…): it now uses the real ones (onChange,initialValue,flat,ghost,key…), checked against the props and types of each component. - Seven stories requested icons by their old name ("Subir", "Carrito") and painted the word: fixed, with a test.
- The texts in the demos (buttons, tables, alerts, amounts) come out in English: the args reach the component already translated and the rest is changed in the preview with
- Server rendering with lazy languages (AMS-1721).
loadMessages(locale, catalogs)resolves the language's catalogue before rendering (server component,loader,getServerSideProps) and its result travels asmessagesinAmsetProvider/LocaleProvider: the HTML comes out translated and hydrates without warnings. It shares thepreloadCatalogcache, does not carry'use client'and rejects if loading fails. AnAmsetProvidernow also receivesmessages. No changes for anyone who does not use it. - The remaining gaps for migrating a front (AMS-1730).
Selectgainsnative(a real<select>: the system picker on mobile, withonChangeEvent) andgroupon the option, which groups under a heading both in the<select>(optgroup) and in the custom panel.ButtongainsloadingText, which replaces the content while submitting, without a spinner.IconButtongainsfullWidth.NumberFieldgainsonChangeEvent. And--amset-field-required-display: nonehides the "required" asterisk (the screen reader still announces it). Everything is additive. - The
catalogslanguages are loaded on demand (AMS-1721). Each tag can be the JSON or a function (fr: () => import('./fr.json')): only the language of the activelocaleis requested, only once, without repeating the load even if the function is written inline, withStrictModeor when returning to a language already seen. While it arrives, the library's texts are shown; if it fails, a console warning is issued,onCatalogErroris called and it is retried when requested again; if the language is changed with another on its way, the late one does not overwrite the new one.preloadCatalog(cargador)preloads (language selector, server). New typesCatalogLoader,CatalogSourceandCatalogErrorHandler. Compatible: an object still appears on the first paint. - API for migrating an existing front (AMS-735). Three things that previously forced rewriting hundreds of handlers:
onClickonButton,IconButtonandButtonLinknow delivers the nativeMouseEvent(withpreventDefault,stopPropagationandcurrentTarget; previously it received the unified press);onChangeEventdelivers the<input>event inTextField,PasswordField,TextArea,CheckboxandSwitch(e.target.value,e.target.checked), without removingonChange; andinputRefreachesNumberFieldandFileUpload, soinputRef.current?.click()opens the file picker from a button of your own. Warning: if you passedonClickreadingpointerType, useonPress, which still gives the unified press. - Entire languages from the provider.
AmsetProviderandLocaleProvideracceptcatalogs({ fr, 'pt-BR': ptBR }, a JSON ofComponente.clave-> text per BCP-47 tag) andAmsetProvideralso acceptsmessages. The catalogue of the activelocaleis used (exact, language or regional) and anything missing falls back to English key by key;messagestakes precedence overcatalogs. The template with all the keys is published in@amset/ui/idiomas/es.jsonanden.json. In development the provider warns once about a language without a catalogue and about keys that do not exist, andvalidateCatalog(json)(with the typesMessageCatalogs,MessageOverrides,MessageKeyandCatalogReport) checks it in a test. No changes for anyone who does not passcatalogs. - The colour stripe of each agenda event measures 3 px again (AMS-1665).
--amset-agenda-slotwas declared twice in the same rule and the second one (the gap between the month and the list, 20 px) overrode the stripe's thickness. The thickness moves to its own variable,--amset-agenda-stripe;--amset-agenda-slotis left only for the gap. - Grouped fields say that they are disabled (AMS-1268).
NumberField,QuantityPicker,CodeFieldandSignatureFieldsetaria-disabledon theirrole="group"when they are disabled: previously only the buttons inside were disabled and a screen reader did not say that the whole field could not be used. - MIT licence (AMS-1688). The package declares
"license": "MIT"and includes theLICENSEfile (previouslyUNLICENSED). No code changes. - Texts that the library wrote in Spanish even when the language was English (AMS-1714). They now come from the message catalogue: "Antes" and "Ahorras un" in the amount, "sube" and "baja" in the data change, "de 5" in the ratings summary, the gallery counter, "con subniveles" in the cascading selector, "sin leer" in the tray, "Eventos del..." in the agenda, "Trabajando" in confirm action and "(se abre en otra pestaña)" in the order status. 12 new keys; nothing changes in Spanish.
- The
Selectannounces that it is required and that it is invalid (AMS-1155). The trigger goes fromrole="button"torole="combobox"(the APG "select-only combobox" pattern) and carriesaria-requiredandaria-invalidwhen appropriate. A screen reader now says "combo box" instead of "button". It affects every field built on it: phone, language and currency selector, VAT rate, month and rows per page. If one of your tests looks for that control withgetByRole('button'), change it togetByRole('combobox'). Visually nothing changes. - The whole Storybook follows the language in the toolbar (AMS-1720). Previously it was only halfway there: with English chosen, the sidebar, the explanation of each story, *Getting started* and the labels in Review and Foundations stayed in Spanish, and the Storybook interface (tabs, settings, props table, shortcuts) came out in English even when Spanish was chosen. Now: the sidebar uses
.storybook/menus.en.json(with the names that Storybook derives from the export without accents corrected inmenus.es.json); the 672 story explanations are in.storybook/historias.en.json; *Getting started* is two markdown files; the bench labels (Canvas,Matrixand the Foundations tables) are insrc/banco/banco.en.jsonandCanvasnow follows the chosen language (without a toolbar it is still Spanish, so the reference screenshots do not change); and what Storybook writes on its own is put into Spanish with.storybook/traductor-de-interfaz.ts. A test (scripts/historias.test.ts) requires every name, explanation and label to have its English version and none to be left over. Storybook's welcome guide and what's-new notice are turned off, as they are its own advertising and cannot be translated. The descriptions in the *Controls* tab, which Storybook fixes when preparing the story and does not repaint, are translated in the DOM itself when English is chosen (only if the translation has the same code in the same order). SiteFooterand@amset/ui/seo(AMS-1750).SiteFooteris the semantic page footer: a<footer>(contentinfoinsideAppShell) with brand, link columns —each one a<nav>with its title as its name—, icon-only social links, a legal strip and controls; external links open in another tab withrel="noopener noreferrer"and announce it to screen readers. Sheetpie-sitio.css, textsSiteFooter.*. The new@amset/ui/seosubpath (server, without dependencies or CSS) providespageMetadata(compatible with Next's Metadata API and with validation that throwsSeoError),checkSeo, the JSON-LD builders (organization,webSite,webPage,breadcrumbList,article,product,faqPage,localBusiness),<JsonLd>with safe escaping inside<script>,sitemapEntries/sitemapXmlandrobotsRules/robotsTxt(blockAllforpre). Additive.Tabssupports tabs without a panel (AMS-1634). A tab withoutcontentdoes not render a panel or carryaria-controls: it is a filter or a mode of the same screen. It is still a tab (arrow keys, focus, selected) and can be mixed with those that do have a panel.Tab.contentbecomes optional; it is additive. TheDialogalready covered the rest of the gaps that the ticket measured in 0.8.0 (header icon and colour, actions in the header, embedded, top-aligned, numeric width, no padding and in a column).- A real usage example for every Storybook story, published in
@amset/ui/ejemplos.json. Taken from the code itself: if the story hasrender, it is its body as is; if not, the JSX tag is synthesised with the real args frommetaand from the story. An example that is missing a required prop (because its value depended on something outside the file, or on themappingof a Storybook control such asiconControl) is discarded instead of being published incomplete.scripts/ejemplos.test.tscompiles each synthesised example against the component's real types.
0.10.2, released October 15, 2026
ToggleChip,ToggleChipGroupandOptionCard(AMS-1534). The two components that were missing to migrate the assistant without losing design.ToggleChipis a chip that is switched on and off (<button aria-pressed>) with its own colour per chip (coloror--amset-chip-color),count,icon, three sizes and a check mark when active;ToggleChipGroupgroups them with a name and line wrapping.OptionCardis aRadioGroupoption painted as a card, withmedia,descriptionand the selected mark: inside the group they are laid out in a grid (--amset-option-card-min). Keyboard and focus are those of a native radio. Own sheetschip-alternable.cssandtarjeta-opcion.css, a Storybook page in Spanish and English and behaviour tests.
0.10.1, released October 15, 2026
- The time field now hydrates the same way in English (AMS-1687). With a 12-hour clock, the server (Node's ICU) wrote a thin space before the "AM"/"PM" and Chrome a normal space: React discarded the HTML and threw error 418 on the
TimeFieldandDateTimePickerpages of amset.tech/en/. The field's separators are normalised to the normal space, and a test pins this down.
0.10.0, released October 15, 2026
- The documentation in English too (AMS-1687).
@amset/ui/fichas.jsoncarriesen: { titulo, descripcion }on each component page, and there is a new export@amset/ui/props.en.json(the description of each prop in English, keyed by the Spanish text). The extractor breaks the build if a page has no version insrc/mensajes/fichas.en.json, and a test watches the coverage and that the Spanish has not changed since it was translated (each translation stores the fingerprint of its source). In Storybook, the documentation pages follow the language selector: with English, the title, text and prop descriptions appear in English (1,758 descriptions, all translated). The standalone stories (their names and comments) and the Getting started pages remain in Spanish. TagsInputandCombobox(AMS-1698). A field of tags that are created as you type (Enter, comma, paste or on leaving the field), without duplicates, withmax,validate,transformandname; andCombobox, the search field that allows free text by default.TagsInput.*strings in es and en.DateTimePickerandMonthPicker(AMS-1698). Date and time in a single field (2026-10-15T09:30, withmin,max,isDateUnavailableandname) and the month and year picker (2026-10, with the months in the active language andminYear/maxYear). They only notify with the complete value. Their own stylesheet,fecha-hora.css, with--amset-datetime-gapand--amset-monthpicker-gap.- Five business fields and five hooks (AMS-1677). New:
MoneyField(money that understands a decimal comma, thousands separator and symbol according to the language, and returns a number),SocialSecurityField(Social Security number (NUSS) with both check digits verified),LicensePlateField(current, provincial and moped number plates),CnaeField(CNAE-2009, with an optional catalogue) andVatRateSelect(21, 10, 4 and 0%). The validators are exported on their own (validateSocialSecurity,validatePlate,validateCnae...), as are the money functions (parseMoney,formatMoney,formatMoneyForEdit). Hooks:useMediaQuery,useDisclosure,useClipboard,useHotkeysanduseAnnouncer. Strings in Spanish and English, a Storybook page and behaviour tests (keyboard and errors on leaving the field) for each one. See the roadmap indocs/roadmap-amset-ui.md. - The library's strings, in Spanish and English (AMS-1687). Every string that a component produced on its own (accessible labels, notices, errors, announcements, summaries, plurals...) now comes from a per-language catalogue (about 560 keys, each in
esanden), according to theLocaleProvider. A language without a catalogue falls back to English; without a provider it stays in Spanish, so nothing changes for anyone already using the library. New: the provider'smessagesprop,translatorFor,useMessages, and an optional last parametermsgin the functions that write a string. Strings with quantities useIntl.PluralRules. Storybook includes a language selector (es, en, ar). Three tests check that there are no unused keys, that the English carries no accents and that no component goes back to hard-coding a Spanish string. - CSS comes in per component, not whole (AMS-1677). Each module in
dist/now imports ITS stylesheet (andbase.css, with the tokens and the reset), so the bundler takes only the CSS of what you use: aButtonin Vite goes from 392 kB to 23 kB of CSS (4 kB gzip), and a screen with 20 components from 395 to 133 kB, with the same pixel result as the single stylesheet. New exports:@amset/ui/estilos/base.cssand@amset/ui/estilos/<hoja>.css. What changes when you upgrade: (1) if you still import@amset/ui/estilos.cssyou pay for the CSS twice: remove it; (2) with the Next Pages Router you needtranspilePackages: ['@amset/ui'](without it,ERR_UNKNOWN_FILE_EXTENSIONwhen building); (3) in Jest, amoduleNameMapperfor.css$. Tested on Vite 8, webpack 5, and Next 16 with App Router and Pages Router. The imports go on the same line as"use client"so as not to move the sourcemaps. - Eleven components can be used from a Server Component (AMS-1677). Previously ALL components carried
"use client", so in Next a server-only layout still shipped the JS ofText,Title,Badge,Separator,EmptyState,VisuallyHidden,CompactGroup,DataList,List,Stack/Grid/Center/ContainerandBlockquote. They no longer carry it: they use no hooks, event handlers or browser APIs. Verified by importing and calling them with Node'sreact-servercondition, and a new test (componentes-servidor.test.ts) prevents a file without the directive from starting to need the browser. No API changes. The components that depend on a provider, on per-language text or on another client component (PageHeader,ButtonGroup,Result) remain marked, on purpose.
0.9.6, released October 15, 2026
- A light theme inside a dark page now looks light. The light values only existed in
:root: a<div data-tema="light">inside a page in dark mode stayed dark, without any error (the theme sample on amset.tech came out dark with "Light" selected). Now[data-tema='light']works on any element, just like the dark one, and where they are nested the innermost one wins. For the text and background of the area to change, the area also needs theamset-canvasclass (or its own colour and background). - Links are no longer underlined.
Linkis rendered without underline, in the accent colour and in bold (the weight is the cue that does not depend on colour: axe stays clean with the link inside a paragraph).underline="always"always underlines it andunderline="hover"on mouse hover. Nor are the following underlined on hover any more: the titles ofCard, the breadcrumbs, theFileListdownload, theStatlink, the filters link, theProductCardlink or those inGlobalAlert.barestill exists and now only removes the weight.
0.9.5, released October 15, 2026
Quality review of twelve components that Aitor flagged on amset.tech (AMS-1701). Everything measured in the browser before and after, with a test that fails without the fix.
DateRangePickernow lets you pick the second end in the other month. With two months in view, the first month followed the focused day, and a day receives focus when the mouse button is pressed, before it is released: the grids shifted one month under the pointer and the click landed somewhere else. Now the view only moves when focus leaves it. Along the way: each month has its name above it, "Next month" looks at the last visible month andDatePicker/DateRangePickerfollow theirvalue(aQuickRangeshortcut now writes to the field).Countdownwith variants:variant="roll"(the default, the digits that change roll),"ring"(a ring that empties continuously) and"text"(the previous look). The screen reader announcements do not change.- Square
ActivityMap: the table caption was being inserted as a cell of the first column; now the days column is as wide as its content, the cells are square, the months go above and the legend lines up with the grid. LocalePickerwith flags (withFlags,LocaleOption.flag,localeFlag), and the placeholder of an emptyDatePickershows each language's letters (yyyy, jj/mm/aaaa, tt.mm.jjjj).PhoneFieldwith the 244 ITU countries and territories, each with its flag (our own SVGs, real geometry) and its name in the active language; you search by typing the name ("Fra"), space keeps searching, Page Up/Page Down jump ten and the mouse wheel now scrolls the list. New propinitialCountry. The options are announced as "Spain (+34)".- The wheel did not scroll in ANY long floating list: the panel also listened to the scrolling of its own list and, on re-measuring itself, sent it back to the top. Fixed in
flotante.tsx, for all of them. ListOption.textValue(search text separate from the label),CountryPrefix.areaCodes.
- The wheel did not scroll in ANY long floating list: the panel also listened to the scrolling of its own list and, on re-measuring itself, sent it back to the top. Fixed in
Amountaligned: its own line height, "/month" next to the price, the discount badge centred with the digits; three sizes in a row share a baseline.Table: sort and filter are two identical 22 px boxes; the active filter is marked with the accent colour and the filled funnel, with no block background. The sticky row header no longer picks up the header grey.Tabswithout a scrollbar: one pixel of negative margin overflowed the list and Windows showed a scrollbar with arrows. If the tabs do not fit, the wheel moves them sideways without a bar.orientation="vertical"is now rendered as a column.SideNavwith the design of the AMSET menu (the assistant's): icon tile, name and description, current page marker, collapsible groups with a count, collapsing to a rail with a tooltip. New backwards-compatible API:description, groups (NavGroup,NavItem),onCollapsedChange,header/footeras a function of{ collapsed }. Widths 13/16/19 rem.AppShellno longer stacks the menu above the content on a narrow screen: a rail remains at the side.- Closed
CollapsibleandAccordionwithout a ghost gap: the closed panel still had a box and took up thegap.
0.9.4, released October 15, 2026
- Two more hydrations that broke on a real website (AMS-1666). They came up when sweeping amset.tech in production, where the HTML is rendered by a CI runner in UTC and the browser is in another time zone and on another day:
QuickRangewith a period set marked the matching shortcut ("this month") according to the day it was when rendering: the server said one and the browser another. Now it marks none until the day is known.RelativeTimewithwithFullDatewrote the full date in the server's time zone ("15:45") and the browser in its own ("17:45"). Now the server's version is accepted the first time and, as soon as the node hydrates, it is remounted with the viewer's time (useInBrowser):suppressHydrationWarningsilences the warning but leaves the server's text.hidratacion.test.tsxnow renders the server in UTC and the browser in Europe/Madrid, and checks that the text ends up correct.
- The
TableAPI moves to English, with the old names still working (AMS-1677). It was the only component that mixed languages (onSortalongsidealSeleccionar,paginacion,enServidor...). Now:serverSide,selectedKeys,onSelectionChange,pagination,configurableColumns,onVisibleColumnsChange,resizableColumns,onRowPress,toolbar; in the columnssortable,filterable,numeric,hiddenByDefault,sticky,rowHeader; and the sort order is{ column, direction }. The old names keep working with a console warning (once per name, never in production) and will be removed in the next major version.onSortdeliversdirectionand alsosentidoin the meantime, so as not to break anyone reading it. Equivalence table in the README, which also had the Table section written against an earlier API that no longer existed. - The library's API, readable by AI agents (AMS-1677). The build now writes
dist/api.jsonwith the real props of each component (type, required or not, comment), taken from the compiler and not from a hand-written list; native HTML attributes are not enumerated, it is noted that they are accepted. From it comedist/AGENTS.md,dist/llms.txtanddist/llms-full.txt, and the package ships a skill,skills/amset-ui/SKILL.md, for anyone building with the library from Claude Code, OpenCode or another agent. New exports:@amset/ui/api.json,/AGENTS.md,/llms.txtand/llms-full.txt. No changes to the components' API. - The
verificarjob builds before testing. The 8 tests of the themes CLI were always skipped in CI becausedist/did not exist yet; now they run.
0.9.3, released October 14, 2026
Timelineno longer breaks hydration because of the spaces in the date (AMS-1666). The span of a bar ("24 Oct – 4 Nov") is written byIntl.DateTimeFormat.formatRange, and with ICU 78 (the one in Node 22.23) it puts thin spaces (U+2009) on either side of the dash, whereas Chrome puts normal spaces. HTML rendered in Node and hydrated in the browser did not match and React threw error 418. Now they are normalised to the normal space. It came to light when removing from the amset.tech website the patch that worked around the timeline: the library's hydration test did not catch it because it runs on a single ICU.
0.9.2, released October 14, 2026
useTabsis exported from the index (AMS-1670). It is the tabs logic without its clothes: for a screen that already renders its own panel and only needs the list with its keyboard behaviour (the arrows wrap around, Home and End, only the active tab is in the tab order, disabled ones are skipped, automatic or manual activation).TabSlot,TabsSetupandTabsPlumbingare also exported. Previously onlyTabsand the gallery used it. Filtering the same list is not a job for tabs: that isSegmentedGroup.- Hydration no longer breaks on a website with SSR or exported as static (AMS-1666). A component that reads "today" or "now" while rendering comes out different on the server and in the browser, and React discards the HTML and re-renders (error 418 in the console, measured in production on amset.tech). It happened in
Calendar,DatePicker,DateRangePicker,QuickRange,MonthAgenda,ShippingPickerandTimeline. Now the first render (the server's and the hydration's) does not know the day, and the browser learns it right after, without an extra frame in an app that only renders on the client:CalendarandMonthAgendawithout a day to anchor them (novalue, nodefaultFocusedValue, noinitialDay) render only the space they occupy, witharia-busy, until "today" arrives; with a given day they are rendered in full from the server.- The today marker (
data-today) and the today line ofTimelineappear once in the browser. ShippingPickerwrites the delivery time once in the browser, unless it is givennow.- There is a test that watches this on ALL the component pages (
hidratacion.test.tsx): it renders one day on the server, hydrates on another and counts the mismatches. A new page is included automatically.
0.9.1, released October 14, 2026
- The text of the 149 component pages, up to date with the code (AMS-1659). The pages in the workbench (and from now on those on amset.tech) still talked about React Aria, which is no longer used, and used the Spanish names of the old API (
etiqueta,tamano,alNavegar,ProveedorIdioma…). Reviewed one by one against the code: real props and values, keyboard and accessibility as they work today, and out with what no longer exists. @amset/ui/fichas.json: each component's page, as data (AMS-1659). One entry per component with its import name, its module, its Spanish title, its family, the text of its page (what it is, when to use it and when not to, keyboard, accessibility, fine details of its appearance; in markdown) and the ids of its stories in the workbench. It is generated from the Storybook stories at build time, so the amset.tech website no longer carries a hand-made copy that went stale.- The
descriptionofDialogaccepts a node (a status tag next to the sentence) and is still announced as a description. Without a description it no longer points witharia-describedbyto an element that does not exist. Dialognow covers what a modal with a branded header needed (AMS-1534, at the request of amset-assistant, which had 42 modals of its own). Everything is optional and what already existed does not change:titlecan be a node;iconsets a custom icon instead of the tone's andaccentgives it colour (--amset-dialog-accent);headerActionsholds buttons between the title and the close cross.width(pixels if it is a number, a CSS measure if it is a string) takes precedence oversize;placement="top"sticks it to the top, for a palette that grows downwards;hideHeaderdoes not render the header but the title is still the accessible name.flushremoves the body padding andbodyColumnmakes it a flex column, for content with its own scrolling.inline: the same box IN the page, as a region with its title. No<dialog>, backdrop, Escape or focus trap, no close cross and no invented "Close": it lets you reuse as a screen panel what elsewhere is a dialog.
0.9.0, released October 14, 2026
QRCode(AMS-1638). A QR code in SVG with no dependencies:<QRCode value label level size margin onError />. Behind it is our own encoder (encodeQr, ISO 18004, byte/UTF-8 mode, versions 1–40, levels L/M/Q/H), checked module by module against theqrcodepackage (2,032 identical matrices with a forced mask), and the rendered SVG is decoded with an independent reader. It is always black on white, also in the dark theme: that is what a scanner reads.Mentions,Watermark,Masonryand antd's image viewer are not built: no measured demand.TreeSelect: a dropdown with the tree inside (AMS-1636). The field is theSelect's and the panel is theTree: choosing a category or a department from a hierarchy without having the tree always in view. One (selection="single", closes on selection) or several ("multiple", stays open and the selection comes out in tree order); any node can be chosen, branch or leaf, except disabled ones. The branches leading to the selection open by themselves and focus goes to the selected row.displayRender,name(one hidden field per key). The keyboard is theTree's plus Down/Up to open and Escape to close. No search inside the panel (theTreedoes not have it) and no connected version of its own: it goes withFormField.Tree: lazy loading and cascading selection (AMS-1637).loadData(clave)requests the children of anisLeaf: falsebranch the first time it is opened; the chevron gives way to a ring (aria-busy) and "Loading…" is announced. If it fails, the branch stays as it was and the chevron retries.cascade(withselection="multiple"): checking a branch checks everything inside it, and a branch with only part of it checked appears "half" checked (and the reader says "partly selected"). The value comes out in tree order; disabled items are not touched. With lazy loading, an already checked branch passes the check on to whatever arrives.- Measured against the ticket: the
Carouselalready had autoplay (automatic, with a pause button, pausing on mouse hover or focus and respecting "reduced motion") and external control (index+onChange); it was not touched. Dragging nodes to move them is left out (it needs a keyboard alternative designed separately).
Cascader: cascading selector (AMS-1635). Country > province > city in a single field, with one column per level, instead of several chainedSelectcomponents that share neither state nor keyboard. The value is the path (['es', 'madrid', 'alcala']), controlled or not.changeOnSelectlets you choose a branch without going down to a leaf;loadData(ruta)requests the children of anisLeaf: falseoption the first time it is opened (the column says "Loading…");displayRender,levelLabels(the name of each level for the screen reader),name(the leaf's key is submitted). Keyboard: up/down arrows through the column, right enters, left goes back, Enter selects, Escape closes. Branches are announced "with sublevels".FormCascaderconnects it to the form. No search or multiple selection yet.- Actually uploading files (AMS-1555).
FileUploadchose and validated, but uploaded nothing: progress, failure and retry were rewritten by every project. Now:Uploader: the drop zone, a bar for each file being uploaded, the reason and "Retry" if it fails, and removing one that is uploading cancels the request. It uploads over HTTP withhttp={{ action, method, headers, data, fieldName, withCredentials }}or wherever you say withcustomRequest({ file, onProgress, signal })(a signed URL, an SDK). How many upload at once:concurrency(3). It notifies withonChange,onUploadedandonFailed, and announces "factura.pdf uploaded" or the failure.useFileUploads: the engine without rendering anything (queue, progress, failure, retry, cancel,pending, andautoStart: false+start()to upload with a "Send" button), to build a different screen.httpUpload,UploadError,isAbort: the default request, withXMLHttpRequest(it is the only one that gives upload progress;fetchdoes not).FileUploadgainsmaxCount(files that do not fit are rejected with their reason),directory(choose a folder; dropping a folder onto it does not traverse it), pasting a file or a screenshot with Ctrl+V, andhideList.- Still true: the type and size filter is a convenience, the server has to check it again.
- Fields consistent with one another and a clearable
NumberField(AMS-1625, AMS-1626).NumberFieldcan be cleared: deleting the text and leaving the field notifies withonChange(NaN)—the "no value" that consumers were already writing— and leaves the field blank. Previously the previous number came back and was submitted. What is not a number (abc) is still discarded.QuantityPickerdoes not change: a quantity cannot be empty.FormNumberFieldtranslates it toundefined.DocumentFieldandIbanFieldacceptonBlur.PhoneFieldacceptsname(it submits the full number in E.164, not the visible part),QuantityPickeracceptsnameandSlideracceptsname(each thumb carries it); they are now submitted in a native form.- The
MultiSelecterror carries the warning icon, like that of the other fields. Slideralready acceptedonBlur(it is an attribute of its<div>); the ticket said it did not.
- Shell, sticky on scroll and attached controls (AMS-1558).
AppShell: header, side column, content and footer in a CSS grid (antd'sLayout,Header,SiderandContentin one). The content is a<main id="contenido" tabindex="-1">, the target of theTopBar's "skip to content"; the header stays fixed and the column stays fixed just below it (it measures its height) with its own scrolling. Below 48 rem the column moves above the content and stops being fixed.siderWidth,mainId,stickyHeader.Affix: a block that stays stuck when scrolling, at the top (offsetTop) or at the bottom (offsetBottom), withonChange(pegado). It isposition: sticky: the container has to be taller than the block and no ancestor can haveoverflow: hiddenorauto.TableOfContentsgainsoffset: with a 64 px fixed header, clicking an entry leaves the section below it and the reading line is counted from its bottom edge. Withoutoffsetit behaves as before.CompactGroup: a field with its button (or several fields) joined with no gaps and with square corners where they meet; antd'sSpace.Compact. The fields inside carryhiddenLabel. It works with any field (text, number, select...) and withButton,IconButtonandButtonLink.- Not done, with their ticket:
Cascader(AMS-1635),TreeSelect(AMS-1636), lazy loading and dragging in theTreeand autoplay in theCarousel(AMS-1637), and the minor pieces (AMS-1638).
@amset/ui/formulario: all fields connected and the API in English (AMS-1557). Previously it connected 8 fields and had the names in Spanish (Formulario,CampoTextoForm,BotonEnviar,resolverDeEsquema), which left two languages in the same import.- Names in English:
Form(propsschema,initialValues,onSubmit,validateOn),SubmitButton,schemaResolverand the typesFormProps,SubmitResponse,StandardSchema,StandardIssue. The usual names keep working with the same props —they are deprecated aliases and will be removed in a major version—: there is nothing to migrate today. - New fields:
FormPasswordField,FormPhoneField,FormDocumentField,FormIbanField,FormCodeField,FormNumberField,FormQuantityPicker,FormSlider(one value),FormRating,FormMultiSelect,FormSegmentedGroup(one option),FormDateRangePicker,FormTimeField,FormColorPicker,FormCardField,FormAddressField,FormSignatureFieldandFormFileUpload. Those with a compound value (card, address) distribute the schema error to each subfield:tarjeta.numberfails on the number, not on the block. FormField: connects ANY other field with a function that receivesvalue,onChange,onBluranderror. It is the equivalent of antd'sForm.Item, and covers anything without its own version, or a component of your own.- The old connected fields are now called
FormTextField,FormTextArea,FormSelect,FormSearchField,FormDatePicker,FormCheckbox,FormSwitchandFormRadioGroup. - Note:
ROOT_FIELDis still'raiz'and not'root'; it is the key that projects already read informState.errors.
- Names in English:
- Bar tone, button shape, group size and broken-photo notice (AMS-1556).
Progressgainstone(success,error,warning,info,neutral; without a tone, accent): a failed upload is painted red and a finished one, green. Colour is not the only thing that says so: put it in the text as well withvalueLabel.Button,ButtonLinkandIconButtongainshape:'default'or'pill'(fully rounded corners) and, only onIconButton,'circle'. Inside aButtonGrouponly the outer corners of the group are rounded. TheShapevocabulary andSHAPESare declared invariantes.ButtonGroupgainssize: it is written once and inherited by theButton,IconButtonandButtonLinkinside that do not state their own (one with its ownsizekeeps it).AvatargainsonError(src): it reports that the photo has not loaded. The avatar already fell back to initials by itself; this is for retrying with another address or keeping a record.- Not needed:
ButtonGroupalready hadorientation="vertical".
- Table: expandable rows, per-row class, maximum height and more (AMS-1554).
renderExpandedRow(row)makes each row expandable: it gains a button witharia-expanded(which points to its detail witharia-controlswhen open) and the detail appears below, at full width.rowExpandable(row)leaves rows that have no detail without a button. It is reachable with the keyboard (right arrow from the row, Enter) and expanding does not count as pressing the row (alPulsarFila). Controllable withexpandedKeys/onExpandedChange, orinitialExpandedKeys.rowClassName(row, index)adds a class to a specific row (an overdue invoice in red).maxHeight('24rem','60vh'): the body scrolls and the header and footer stay fixed. It was the--amset-table-heightvariable; now it is a prop.TableColumn.pegadaaccepts'end'(the actions column stays at the end when scrolling horizontally);trueand'start'still stick to the start.TableColumn.ellipsistruncates long text with "…" on one line and leaves the full text in thetitle.- Fixed: with the table scrolled, the sticky row header (
th) was painted over the "Number" column header, and the fixed footer ended up below the sticky columns. - Not needed: the footer totals (
TableColumn.footer) and the sticky header already existed. - Still not available:
colSpanper cell,bordered,tableLayout,showHeader,caption.
- Render functions (AMS-1550). Until now an option, a cell or a day was text plus an icon: as soon as an avatar, two lines or a price was needed, the component was discarded. Now the piece itself can be rendered. The rule is that of
Chip:labelis what is read and the function renders what is seen, so the accessible name does not change and the piece remains selectable with mouse and keyboard.renderOption(option, { selected, disabled })inSelect,MultiSelect,SearchField,SelectableListandVirtualList(the latter only calls it for the rendered rows).renderValue(option)inSelect: the selection inside the closed field.renderTag(option)inMultiSelect: each tag of the selection.renderItem(item, { selected, disabled })inTransferList.renderDay(date, number)inCalendar: what goes inside each day. The day's name does not change.renderPage(page, { current })inPagination: the content of each page's button (only withshape="full").renderCrumb(crumb, { current })inBreadcrumbs.TableColumn.rendernow also receivesindex, the position of the row in the visible page.- The
OptionRenderandOptionRenderStatetypes are exported. - Everything is additive. Still missing is what antd calls
expandedRowRenderandsummaryin the table: they belong to AMS-1554.
0.8.0, released October 13, 2026
- Who closes a layer and what opens it (AMS-1553).
DialoggainsisDismissable(clicking outside closes it; withfalse, it only goes away through its buttons, Escape oropen) andisKeyboardDismissDisabled, the same two thatDraweralready had. It is antd'smaskClosable.kind="alert"still does not close when clicking outside.TooltipgainsopenOn('hover' | 'focus' | 'press', by default['hover', 'focus']). With'press'it toggles on press and closes when pressing outside: that is what makes it visible on a touch screen. The element's ownonClickis respected.DropdowngainsopenOn('press' | 'contextmenu'). With'contextmenu'it opens with a right click and the browser's menu does not appear. There is no'hover', on purpose: the panel is modal.- There is no
zIndexor portal container, and it is not an oversight: layers go in the browser's top layer, which sits above anyz-index(measured with 2147483647) and works inside a shadow DOM. A panel opened from inside another library's modal lives in that modal, so it remains clickable.
- Loading state (AMS-1552).
loading(andloadingText, for what it says) reaches the components that did not have it:Switch,Select,MultiSelect,SearchField,SelectableList,VirtualList,DialogandDrawer.Switch: while waiting for confirmation it does NOT change value or notify, but stays in the tab order (disabling it would take the focus away), with a spinner in the thumb.Select,MultiSelect,SearchField: the field is marked busy (aria-busy), shows a ring and the panel shows aCargando…row. Any options already present stay.SelectableList,VirtualList: busy, with the row underneath (infinite scroll).Dialog,Drawer: the content is replaced by a skeleton and announced; the title and the buttons stay.
Spinnergainsdelay(it does not appear until that time has passed, so that a 100 ms load does not flicker) and can wrap a block withchildren: it leaves it greyed out, inert and with the spinner on top.inputRefonTextField,PasswordFieldandTextArea: the node of the actual<input>or<textarea>, not that of the box. To focus a field when opening inline editing, select a token that is about to be copied or return focus to a search box. It accepts an object and a function, and coexists withautoFocus(the attribute still reaches the DOM). It was what prevented migrating the amset-assistant fields that are focused by hand.- A field's
idreaches its control (AMS-1488).TextArea,TimeField,DatePickerandDateRangePickeraccepted it and threw it away;MultiSelectandSearchFieldleft it on the outer<div>. Now, in a field with an<input>theforof its label points to it, and in one whose control is a row of segments (date, time, range) it lands on the group itself. PhoneField,QuantityPicker,TimeField,DatePickerandDateRangePickeracceptidin the type: at runtime they already forwarded it, but the compiler did not allow it to be written.
0.7.0, released October 13, 2026
All components pass through the attributes of their element (AMS-1549). Previously 14 out of 158 did; now all those that render DOM do. It is an additive change: measured against 0.6.2, no prop has been removed or had its type changed, so upgrading should not break anyone's build.
### What you can do now
<Tag id="estado-pago" title="Pagada el 12/10" data-testid="etiqueta" style={{ marginInlineStart: 8 }}>
Pagada
</Tag>Any attribute of the element that the component renders at its root: id, title, lang, data-*, aria-*, style and the mouse and keyboard handlers. Two rules worth knowing:
classNameis added to the library's classes andstyleis merged with the component's. Neither of them replaces it.- In a field (
TextField,Select,NumberField…) the attributes go to the control —that is where anyone writing adata-testidor anautoCompleteexpects them—, andclassNameandstylego to the box, which is what is seen.
DomProps<'div'> is also exported, to type a wrapper of your own without listing attributes by hand.
### What is NOT passed through, and why
There are element props that a component sets itself, and one from outside would be accepted and silently discarded. In those cases the type does not accept them, so you find out when compiling instead of having to check that nothing happens:
| Component | Does not accept | Use |
|---|---|---|
Dialog, Drawer, IdleWarning, CommandPalette | onCancel, onClose, onClick, role, aria-labelledby, aria-describedby | onOpenChange, title, description |
Dropdown, ConfirmAction, ShareButton, NotificationTray, ActionMenu | id, role, tabIndex, onKeyDown, onClick, aria-label, aria-labelledby, aria-describedby | title, onOpenChange |
Tooltip | id, role | — |
OverlayCloseButton | onClick | onPress |
Checkbox, Switch | onKeyDown, onKeyUp, onPointerEnter, onPointerLeave, onPointerDown | wrap it in your own element |
Carousel | onFocus, onBlur | — |
Tree, SelectableList, VirtualList, SortableList | onKeyDown, onFocus, onBlur, role, tabIndex | — |
DocumentField, IbanField | onBlur | — |
### Other changes
- In a text field, an external
aria-describedbyis added to the help text and the error instead of overwriting them (TextField,TextArea,PasswordField). Cardseparates in the type what governs each form (static, pressable, link); what it accepted does not change.FieldRootPassThroughandFieldInputPassThroughnow acceptstyle.- Three tests check the result in the library itself: that the attribute arrives, that it lands on the root and not on an internal piece, and that an external handler is called.
Before 0.7.0
There was no changelog. Earlier versions are in the GitLab history.
