1 hours ago
astryx

Astryx v0.5.3

[!WARNING] Stepper context compatibility: v0.5.3 changed the package-exported StepperContextValue / useStepperContext shape. Ordinary <Stepper> and <Step> usage is unaffected, but consumers that call the context hook directly or construct StepperContextValue should remain on v0.5.2 while a source-compatible repair is evaluated. See #5659.

Astryx 0.5.3 — all @astryxdesign/* packages ship at this version.

npx astryx upgrade --apply

@astryxdesign/core

New Components

  • Add built-in popover, bottom-sheet, and compact-touch adaptive presentation policies to DropdownMenu, MoreMenu, and ContextMenu. (#5395)
  • Add popover, bottom-sheet, and adaptive presentation options to Selector and MultiSelector, with docsite examples for both bottom-sheet variants. (#5395)
  • Add isReadOnly to Selector and MultiSelector so selected values remain focusable and form-submittable without exposing selection menus or editing affordances. (#5805)
  • Add the opt-in theme-local token contract for maintained theme families. (#5844)
  • Add elevation prop to ToggleButton for floating (FAB-style) toggles, mirroring Button; retained inside a ToggleButtonGroup. (#6012)

New Features

  • Add structured accessibility requirements and theme coverage support to component documentation. (#5713)

  • Banner: the header's supporting line now carries a stable theme target, astryx-banner-description. (#5483) Only the header, the status icon and the content panel were themeable before, so a theme restyling the description — its colour, its type, or the space between it and the title — had to reach in with a structural selector like .astryx-banner > div:nth-child(2) > div:nth-child(2). Purely additive: no existing class, data attribute, or style changes. Nothing else in the header becomes a target. The end area is a layout row — flex, wrap, and the edge compensation that lets its buttons overhang the header padding — not a painted surface, and a theme that wants the header to grow around its buttons instead of letting them overhang sets padding-block on the existing banner target, which reaches the same height without exposing a private margin. The title, the two controls and the text column are likewise left alone: the column paints nothing (display: flex; flex-direction: column; gap: 0) and the space it owns is expressible on banner-description, while the title and the controls already render the way the consuming theme wants them.

  • Add nativePicker to DateTimeInput for browser and OS date/time pickers, with Astryx time fallbacks for seconds, custom increments, and preset options. Native fields follow DateInput's compact minimum sizing in fit-content layouts. (#5620)

  • Let themes add typed Heading visual roles with a safe semantic-level fallback when the owning theme styles are unavailable. (#6026)

  • RadioListItem and CheckboxListItem accept rich label and description (#5257) RadioListItem typed label and description as string while its sibling CheckboxListItem already typed label as ReactNode — so the same slot had two contracts, and an app whose option descriptions carry links could not type them on either component. Both now take ReactNode; the runtime already rendered it.

    RadioListItem gains the aria-label escape hatch CheckboxListItem established, with the same meaning: a plain-text accessible name for the control. The radio differs in one way worth knowing — it points at its visible label for its accessible name, so a rich label still names it from its own text, and aria-label is there to narrow a name that reads badly rather than to supply a missing one. aria-label now lands on the radio instead of the row <div>, where ARIA ignored it.

  • Stepper: --step-connector-gap, so a theme can stop the on-track connector short of the indicator The on-track layouts draw the connector as one segment either side of the node. A theme that wants the track to leave a hole around the indicator had to reach the two segments separately, and they are only distinguishable by sibling position — which changes with indicator="none".

    One public var does it instead, declared on the Stepper root because component vars are root-owned: a theme writes stepper: {base: {'--step-connector-gap': '4px'}} and every connector inherits it. Astryx spends it on whichever side each segment faces the node from, so the pair leaves a symmetric hole and the caller never names the pieces. 0px by default: the shipped track still reads as one unbroken line.

    Measured in Chromium against a built theme override, reading painted pixels down a 12px segment:

    | value | clipped away | stepper height | | ------- | ------------- | -------------- | | 6px | 6px | unchanged | | -4px | 0 | unchanged | | 1rem | capped to 8px | unchanged | | 999px | capped to 8px | unchanged | | 10% | 1px (of 12px) | unchanged | | 50% | capped to 6px | unchanged |

    Four things that had to be true and are:

    A theme override reaches it. The default is declared once on the root, not on each connector. Declared per-connector, every connector re-declared 0px on itself, and a value declared on an element beats an inherited one — so a generated stepper override compiled cleanly and changed nothing.

    The value is bounded, and both halves earn it — neither for padding's reasons. max(0px, …) because inset() accepts a negative length: Chromium computes inset(0 0 -4px 0) as written rather than clamping it the way it clamps negative padding, so the floor has to be declared. min(…, --spacing-2) — the flexible segment's own min-height — so an oversized gap leaves a short track rather than an unbounded one. Neither can grow the Stepper; a clip cannot change layout. (An earlier padding-based revision grew a three-step Stepper 108px → 144px at 1rem.)

    The horizontal clip mirrors under dir="rtl". clip-path: inset() is physical — top/right/bottom/left, no logical form — while the row itself reverses. Left unflipped, the leading segment sits to the right of the node in RTL and still clipped its right edge, so the hole opened at the join between steps instead of at the indicator. Measured before the fix: con0 x=622, indicator x=606, clips RIGHT edge. After: clips LEFT edge, with LTR unchanged. The block axis needs no handling — dir does not reverse it.

    One declaration covers both layers. The gap has to reach the track (the segment's own background) and the accent fill (an absolutely placed ::before). Spending it on each separately meant two declarations on two boxes, so a percentage resolved against a different containing block for each and stopped them ~1.2px apart. A single clip-path: inset(…) on the segment clips the element and its pseudo-element together against one reference box, so every accepted value behaves identically on both — which is what #5824 requires of a public input across its full value domain. Clipping also cannot change layout, so the node the segment positions cannot move.

    No indicator, no gap. indicator="none" renders no node, so a gap there is a hole in a track that is meant to be continuous.

    Any CSS length or percentage is accepted and behaves the same way on both layers. A percentage resolves against each segment's own box, so a fixed and a flexible segment clip by slightly different amounts from one declared value — cosmetic, bounded by the cap, and recorded as accepted rather than fixed.

    Why a custom property and not a guaranteed CSS property. A theme target reaches the element, never its ::before. Measured against a built theme override on step-connector: paddingBlock: 6px produces no hole at all — the background paints to its border box and the fill is out of reach — its only effect being the Stepper growing 108px → 120px; paddingBlockEnd: 6px produces no hole either, and addresses only one of the two edges. Only the component can clip both layers together, mirror per axis and direction, and clamp first.

    Adds Stepper.spec.md, the canonical owning record for this public property, carrying that admission argument, the value contract, and the anatomy-to-target map. Stepper.doc.mjs gains the anatomy entries its existing stepper, step, and step-connector targets never had, so every current target is anchored to a described part.

    Supersedes the segment variant this PR previously proposed. That exposed lead / rail / content as public theming vocabulary, which does not hold up: the words never appeared in the generated docs, they emit bare lead / content classes where a consumer's own stylesheet can collide with them, and lead means different geometry per orientation. The pieces are how this layout happens to be drawn today, not a contract.

  • Stepper's horizontalOptions.collapsedVariant lets a flow choose withLabelAndControls, withLabel, or hiddenLabel for its compact presentation. Use withLabel when the surrounding flow owns Back/Continue, or hiddenLabel when surrounding UI owns both the current-step heading and navigation and only a bare progress track is needed. The default preserves both label and controls, its controls require onStepClick, and every step keeps its name in the accessible sequence at any width. (#5659)

  • Stepper's horizontalOptions.minimumStepWidth configures the per-step width at which a horizontal Stepper collapses. Numbers are interpreted as pixels and strings accept CSS lengths such as 7rem, calc(6rem + 8px), and custom properties. The browser resolves string units through an invisible measurement element, and changes to the resolved value update the compact layout. Omitting the option preserves the existing 112px threshold. (#5659)

  • Stepper: add astryx-step-label and astryx-step-description theme targets. (#5728) Both text parts declare their own typography and color, so themes cannot reach them through the step target by inheritance. The new targets apply in both indicator positions and reflect progress and status.

    step-label also reflects disabled, because the label owns Stepper's disabled text paint. step-description does not. The new targets change no default style.

  • Stepper collapses itself in narrow containers instead of leaving each consumer to hand-roll a fallback: a horizontal stepper measures its own width and, once a step has under horizontalOptions.minimumStepWidth (112px by default), drops the labels to a bare track and uses the configured collapsedVariant beneath it. The breakpoint follows the step count rather than the viewport. Both separated and on-track leave their compact track presentational; navigation moves to named prev/next controls when configured and when onStepClick is set. On-track indicators stay on the rail without repeating the active indicator beside the compact label. The full sequence stays intact for screen readers throughout. (#5659) [fix] Step labels hold to a single line and ellipsize rather than wrapping and breaking mid-word, so a row of horizontal steps keeps one height and the track under it stays straight. The full label is still carried in the step's accessible name.

    [fix] The gap between connector segments is now --spacing-1, matching the connector's own thickness, so the track reads as one dashed line at any theme scale.

  • Add semantic Table row statuses while restoring custom-marker compatibility. Named custom icons keep their released Icon color mapping; raw CSS custom icons now use the caller's paint as required by the current contract. Canary users relying on implicit glyphs should switch from color to status. (#5832)

  • TabList: add an isFullBleed prop so a tab bar can bleed out to its container's inline content edges instead of requiring hand-written negative-margin CSS (#2622). Like Divider's isFullBleed, it cancels the nearest padded Layout container's --container-padding-inline-* custom properties with negative margins; the inner strip pads back by the amount the bleed exceeds a tab stop's own padding so edge labels remain aligned to the content inset. It is inline-only: TabList owns the inline full bleed, and the container owns the block-end dock. For that, LayoutHeader gains a paddingBlockEnd per-edge override in Section's existing spelling — paddingBlockEnd={0} docks the header's last child on its bottom edge so a tab strip's underline meets hasDivider at any header padding. The detail-page template now uses both props, aligns its ghost panel toggle with the container inset, and no longer carries any hand-written tab-row CSS.

  • Add nativePicker to TimeInput so coarse pointers use the browser/OS time picker by default, with always and never overrides. Seconds and custom increments retain Astryx's typed field. (#5811)

Fixes

  • Keep AppShell's section top bar solid in auto-height mode while content scrolls beneath it. (#5873)

  • Extend attached field-status backgrounds behind the lower half of their controls so rounded and pill-shaped inputs connect without visible gaps while the control remains visually above and receives pointer input across the overlap. (#5769)

  • BottomSheet now keeps the iOS Safari browser-bar edge consistent with the sheet surface for both modal and non-modal presentations. (#5373)

  • Keep loading-button spinners at full contrast while interaction is blocked, and suppress pressed feedback for disabled and loading buttons. (#5627)

  • Carousel: mirror the single-edge fade gradients under RTL so the mask fades the physical edge that actually hides content (overflowStart/overflowEnd are logical edges, the gradients were always physical left/right) (#5586)

  • ChatComposerInput no longer discards a pending draft when you click the composer's padding and press ArrowUp. Focusing a contentEditable collapses the caret to the start of the draft, which is the one position where ArrowUp means "recall history", so the first ArrowUp after that click replaced what you had typed. The composer now places the caret after the draft when it focuses itself — clicking the space after the text means "put me there" — so ArrowUp moves the caret with a draft present and still recalls history when the composer is empty. Multi-line caret navigation is unchanged. (#6051)

  • ChatComposer: add a keyboard-only focus ring around the composer body when its editor receives focus (#5648) The ring uses the shared theme focus tokens and does not appear for pointer focus or when an internal action button owns focus.

  • Chat/useChatStreamScroll: an upward scroll releases auto-follow in both motion modes, and only the reader can release it. While following, the hook owns the container's position: it disables CSS scroll anchoring on the scroll element, so the only move the browser makes on its own is the resize clamp onto the bottom, and any other upward move is read as the reader. A wheel or drag a nested scroller consumes, or a block collapsing above the viewport, no longer touches the lock either way; unlocked, anchoring is restored. The wheel and touch listeners are gone. jumpToBottom also cancels the spring's pending frame, so animation loops cannot stack. (#5662, #5663)

  • ChatToolCalls now announces pending, running, complete, and failed statuses to assistive technology, including expandable rows and collapsed tool-call groups. (#5666)

  • ChatComposerInput: ArrowUp/Down only recall message history at the text boundaries, so the caret can move between lines of a multi-line draft (#4284)

  • CheckIndicator: start the docsite properties preview in the checked state (#5972) Seeds a checked playground default so the properties-tab preview shows a visible indicator on first load instead of an empty stage.

  • Collapsible's trigger label now fills the row instead of hugging its own content, so a composed trigger can put something at the far edge next to the chevron. The trigger is a space-between flex row, but its label span had no flex-grow — so the free space collected between the label and the chevron, and a trigger built as <HStack> with a right-hand element (a date, a count, a status) had that element parked against the label with a gap after it, unable to reach the edge that space-between implies. flexGrow: 1 on the label is the whole change. For a plain text trigger nothing moves: the label was already flush to the start edge and the chevron to the end, and the box that grew is one the text does not fill. The flex floor is deliberately left at auto, so no label can now be squeezed narrower than its own content and start overlapping the chevron. (#5933)

  • DropdownMenuRadioItem: add playground wrapper and wire wrapper selection state for docsite preview (#5917) Wraps DropdownMenuRadioItem in DropdownMenuRadioGroup wrapper and keeps the wrapper's selection independent from the item's value knob, so aria-checked stays false until the item is activated and updates on click.

  • Keep DropdownMenu and submenu flyouts inside the viewport with safe inline gutters and viewport-aware height limits. Only overflowing menus become internal scroll containers, while menuWidth keeps its existing minimum-width behavior up to the available space. (#5395) [feat] Add an opt-in presentation prop for data-driven DropdownMenu instances so products can render the same actions as an anchored popover or a modal bottom sheet according to their own responsive input policy.

  • FieldLabel: a field's description now sits flush under its label without breaking existing label layout overrides. (#5673) A label and its description are one block of text, but nothing in FieldLabel said so. It returned a fragment, leaving the <label> and the description <span> as bare siblings of whatever column happened to hold them — so the space between them was set by that parent's gap, the same declaration that separates the label group from the control below it. No caller could close the pair without also pulling the control up against the description, and each had picked its own value.

    Measured in Chromium as description.top - label.bottom:

    | | label → description | description → control | | ------------------------- | ------------------- | ----------------------------------- | | Field, TextInput | 4px → 0px | 4px → 4px | | CheckboxInput, Switch | 2px → 0px | n/a — control sits beside the label |

    The label and description now share a wrapper of their own, so the space between them is theirs to set rather than a side effect of the caller's column. Only the pair closes up: the description → control gap is unchanged, so fields keep their existing rhythm. CheckboxInput and Switch each carried a 2px label wrapper to do this job locally, which the shared wrapper makes redundant, so all three callers now agree instead of each choosing a value.

    A hidden label group takes display: contents, so the wrapper box leaves the caller's layout entirely and the sr-only label and description stay out of flow exactly as they were — a hidden label still costs no space and draws no gap.

    This is one change in FieldLabel rather than a change across the ~20 input components, because every input reaches its label through Field.

  • useFocusTrap only restores focus when focus actually entered the trap while it was active. (#5651) useFocusTrap captured document.activeElement on activation and restored focus to it on deactivation whenever focus would otherwise be lost to <body>. For popups that deliberately keep DOM focus on their trigger — a Typeahead or PowerSearch listbox opened with role: "none" and hasAutoFocus: false — the trap never receives focus, so the restore fired on outside-click dismissal and re-focused the anchor input. Because the input was then already focused, clicking it again fired no focus event and hasEntriesOnFocus could not reopen the menu — the control was stuck until a second outside click.

    The restore effect now tracks whether focus entered the trap container at any point while it was active (via a focusin listener). If focus never entered, the restore is skipped entirely. Popups that do take focus — Dialog, DropdownMenu, a Typeahead option click — are unaffected.

  • Core: track keyboard and pointer modality once per document instead of initializing global listeners from every consuming component. (#5881)

  • LayoutHeader: add playground wrapper and default children for docsite preview (#5918) Prevents the properties-tab preview on the docsite from rendering an empty stage by wrapping LayoutHeader inside a Layout scaffold with representative header text.

  • LayoutPanel: add playground wrapper and default children for docsite preview (#5919) Prevents the properties-tab preview on the docsite from rendering an empty stage by wrapping LayoutPanel inside a Layout scaffold with representative panel content in start slot.

  • Streamed Markdown no longer goes blank when the line still arriving contains an escaped pipe. A \| is literal text, not a table-cell delimiter, so a line carrying only escaped pipes is ordinary prose and renders as it streams instead of being held back as an unfinished table header. Genuine partial table syntax is still suppressed, and incremental parsing stays bounded to the stream tail. (#6051)

  • Popover layers now cap explicit widths and match-trigger sizing to the available viewport with alignment-aware token safe-area gutters, preserving trigger alignment while keeping the painted surface at least one spacing token from both viewport edges. Long content scrolls inside the layer instead of forcing page overflow on narrow viewports. Repeated resize and content-change signals coalesce overflow measurement to once per animation frame. Pointer-activated dialog popovers focus the labeled dialog container so the first action does not appear preselected, while keyboard activation still focuses the first content control. Read-only content uses the same container target without revealing the fallback close button, while preserving Tab access to that fallback escape control. (#5373)

  • Reuse DateRangeInput for PowerSearch date-range values so endpoint selection always emits an ordered range. (#6004)

  • useResizable: percentage configuration with an explicit basis (AST-010) Implements the accepted AST-010 contract. Percentages configure a pixel size; they never create a second, responsive sizing mode.

  • ResizeHandle: a drag survives the cursor crossing an embedded frame. (#5297) The handle listened for pointermove/pointerup on window without taking pointer capture, so the browser hit-tested every later event — and the moment the cursor entered an <iframe> inside the resizable region the events went to the guest document instead. Measured in Chromium, the host received 0 of 25 pointermoves once the cursor was over the frame, the panel stopped tracking, and the pointerup was never heard: the handle stayed armed with data-resizing set and the body cursor/user-select overrides stuck. The drag now takes pointer capture on the grab zone on pointerdown, so the whole gesture is delivered there whatever is underneath, and the move/up/cancel handlers sit on that element rather than on window (the same shape as Slider and BottomSheet).

  • Breadcrumbs mirrors its built-in slash separator in right-to-left layouts (#5365) Fixes #5364.

  • sharedResizeObserver: independent subscriptions per element (#5817) The module held one callback per elementcallbacks.set(element, callback) overwrote. A second hook observing the same node silently replaced the first, and either one calling unobserveResize(element) blinded the other.

    Two hooks on one element is ordinary rather than exotic: a TabList root, a useOverflow container and a useTruncation target are all nodes another hook may reasonably watch.

    observeResize now returns an unsubscribe that removes only its own registration, and every caller in the package uses it. unobserveResize(element, callback) does the same by hand; the callback-less unobserveResize(element) still drops every callback on the element and stays for a caller that owns its element outright.

    Dispatch snapshots the callback keys, so a callback may unsubscribe while the batch is running without skipping its neighbour.

    Prerequisite for AST-010 §Implementation-requirements 9, kept separate so it is reviewable on its own. No component behaviour changes: 8534 core tests pass, and the five observer regressions fail against the old module.

  • Spinner: a narrow flex host no longer compresses the box and clips the ring (#5484) The spinner's box carried overflow: hidden from the canvas ring it no longer draws. It clipped nothing — the painted circle is inscribed in the box, so hiding or showing the overflow renders the same pixels at every size and shade — but a flex item whose overflow is not visible has an automatic minimum size of zero. That left the box with no floor: a flex host narrower than the spinner compressed it while the ring kept drawing at the size its own attributes ask for, and the clip then cut the ring off at the box edge, silently, because a sliced ring still spins.

    Ordinary layouts reached it. A md spinner beside a label in a 140px row rendered a 16px box around a 20px ring; an lg spinner next to a flex: 1 0 100px sibling lost half of its ring. The clip is gone and the box is flex-shrink: 0, so the box and the ring stay one measurement and a spinner that does not fit overflows its host visibly instead. Nothing moves for a spinner whose host already fitted it.

  • Spinner: the drawn frame follows a themed --spinner-diameter, so a themed ring is no longer clipped or off-centre. (#5214)

    --spinner-diameter and --spinner-stroke-width set the ring in CSS, and the box the ring sits in is composed from those same two vars — but the <svg> was sized and given its viewBox in JS, from the size's own constants. Theming the diameter therefore left the frame behind: the svg stayed at its default while the box shrank around it, and an overflowing grid item aligns to start rather than centre. Measured in Chromium across the four sizes, a themed ring rendered 1.5-3.3px off-centre with its far edge cropped.

    The svg is now sized in CSS from --_spinner-box-size — the same composed var the span is sized from — with no viewBox, so one user unit is one pixel and the frame moves with the box. (Not a percentage: the span is a grid whose area is not always definite in both axes, and an unresolved percentage height on an SVG falls back to the replaced-element default of 150px.) The px width/ height attributes remain as the no-stylesheet fallback, as r and stroke-width already were. Both circles centre on cx/cy="50%", and the arc's twelve-o'clock offset is a CSS rotation about the shape's own box rather than an SVG transform about a centre in user units.

    No change to the default render at any size — same box, ring, stroke and sweep, verified against a build of main. What changes is that the documented claim "the rendered box … follows automatically" is now true.

  • Table's sortable header button now follows its column's align, so an align: 'end' or align: 'center' column no longer gets a start-hugging header label sitting above right-aligned figures. Sorting wraps the header in a full-width flex button, which the textAlign that align sets on the cell cannot position; the alignment is now carried onto the button's main axis with a flow-relative justify-content, so it keeps mirroring under RTL. (#5928)

  • Route table-row outcomes and completed or failed tool calls through the theme's semantic icon registry. (#5671)

  • themingTargets.test.ts now discovers component sources at any depth under src, not only in a top-level directory. (#5784) Sources nested a level down — Table/plugins/<name>/ — were silently exempt from the guard, which is the same drift #3741 was filed to prevent. Nothing was failing (no nested source rendered a themeProps() class before this release), so this closes the hole rather than fixing a live break: the guard goes from 294 to 302 assertions.

  • Timestamp relative and compact-relative labels now follow the active provider locale, including locale-specific plural rules and word order. (#5859)

  • Toast: the card's shadow is no longer clipped away. (#5547) Each toast's grid row used overflow: hidden throughout its lifetime. The clip is load-bearing while the row opens and closes — it makes the toast read as folding into the stack — but at rest it hugs the card's border box with zero slack on every side and cuts off every shadow the card casts. Astryx's own --shadow-med was declared and invisible: against a white page, every sampled pixel below a stock toast was pure white.

    The row now keeps its cross-engine overflow: hidden boundary during entry and exit, and releases it to overflow: visible only after the opening transition settles. Dismissal restores the clip synchronously. The wrapper keeps its ordinary pointer boundary, so a second click while the toast is still visible is absorbed by the toast rather than falling through to an obscured control underneath.

    This avoids overflow-clip-margin, which WebKit 26.5 does not support, while preserving the exact paint boundary the exit shipped with before this fix.

    Settled state is held on the mounted row rather than in a set of toast ids on the viewport, so it cannot outlive the row it describes. A row leaves the DOM by more paths than dismissal — maxVisible evicts the oldest when a newer toast arrives, and a uniqueID overwrite swaps a new entry into a replaced toast's place — and on neither path does anything on the dismissal path run. An evicted toast that resurfaces once the stack drains therefore mounts clipped and runs its own entry transition, instead of releasing the clip over a row that is still opening.

    One further lifecycle guard: only the row's own transition is read, since grid-template-rows is not private to the wrapper and transitionend bubbles from any descendant animating its own grid. Reading a descendant's event as the row's own releases the clip before the row has finished opening, and during exit it unmounts the toast mid-collapse.

    Below a settled stock toast on a white page, sampling straight down from the card's bottom border box: 255,255,255 at every offset before; after, the shadow paints 223 at +0px and fades 237 → 243 → 247 → 250 → 253 → 254, reaching white again at +12px. During exit the row clips again, so anything outside the shrinking row is neither painted nor hit-testable — while the wrapper itself keeps the ordinary pointer boundary it has always had, and still absorbs a click aimed at a toast that is still on screen.

  • Reset Toast swipe state when a second touch begins so native pinch and two-finger gestures remain available, and clear transient drag styles before a successful swipe dismissal. (#5676)

  • ToastViewport resets the UA popover width, so an end-positioned toast lands on the end edge again. (#5822) The viewport reaches the top layer through popover="manual", and the UA stylesheet gives every popover width: fit-content. Since the placement rework the viewport is positioned by spanning the inline axis and aligning within itself, and a shrink-wrapped box cannot span — both inset edges cannot be honoured, so the box resolves against the start edge and align-items: flex-end aligns the toast to the right of a box sitting on the left. Measured in Chromium at 1200px: a 438px viewport at x=0, with the default bottomEnd toast at x=19 instead of x=781. The reset block already neutralised inset, margin, border and background; width belongs with them.

  • Tokenizer: render and interact in the docsite properties preview (#5982) Seeds playground defaults for the required value array so the properties-tab preview shows a labeled field with tokens on first load instead of the missing-required-props placeholder, and wires the preview's onChange bridge back to the controlled value so removing a token updates the field.

  • TreeList: respect consumer onKeyDown preventDefault cancellation for APG tree keyboard navigation (#5606) TreeList previously processed built-in APG keyboard navigation on the inner <ul role="tree"> before consumer onKeyDown ran on the root <div>, preventing consumer event.preventDefault() from suppressing built-in arrow navigation.

    Root onKeyDown now invokes consumer onKeyDown on the root container first and checks event.defaultPrevented before handling internal tree navigation for keydown events originating inside the <ul role="tree">. Calling event.preventDefault() in onKeyDown now successfully cancels built-in navigation and leaves focus and roving tabindex unchanged while preserving root handler target contracts.

  • Typeahead: the field keeps its width when a value is selected, and the value stays out of the end controls (#5560) Two halves of one promise from the input-field family contract (docs/families/input-fields.md): FR1, a field's available width does not change because its value did; and FR2, a visible end affordance does not have field content painted under it.

    FR1 — the input keeps its place. Every other field in the family gets a stable width for free: the <input> stays in flow, and the field is as wide as the input's own intrinsic width. Typeahead took the input out of flow and zeroed its width while a token showed, so the field was left measuring the token. In any shrink-to-fit parent it snapped to the value's length. Block-level parents hid it, because they fill their container whatever their content is, which is why no story caught it. The input now keeps its place in the row and its own width — it is only made invisible and inert — and the token is painted over that space rather than beside it. In flow the token would add its own width instead, which is the same value-dependent sizing from the other direction: a long value would grow the field.

    FR2 — the value is bounded by a content lane. The input and the token share a content lane: an ordinary flex item, flex: 1 with min-width: 0, that ends exactly where the end lane begins. That is TextInput's own arrangement — the lane takes the free space so the end controls sit in the corner, and yields all of it when the field is narrow, so a narrow field cannot overflow. The token is anchored at both of the lane's inline edges, so a long value ellipsizes at the lane's edge instead of reaching the controls. Positioned against the whole field instead, as the first revision of this change did, it had no idea where those controls start.

    Measured in Chromium. Widths are the field's border box, field in a max-content parent, Field.width otherwise unset:

    | | empty | short value | long value | | --------------------------------- | ----- | ----------- | ------------ | | TextInput (family baseline) | 199px | 227px | 227px | | Typeahead before | 199px | 54.7px | 224.09px | | Typeahead after | 199px | 223px | 223px | | Typeahead in InputGroup, before | 397px | 252.7px | 422.09px | | Typeahead in InputGroup, after | 397px | 421px | 421px |

    The 24px between the empty and valued columns is the clear button entering the row — ordinary for any field whose clear is conditional, it does not vary with the value, and TextInput's is 28px.

    Overlap is the value's trailing edge past the clear button's leading edge; escape is how far the value reaches past the field's border. The middle column is this change's own first revision, which fixed the width and made the overlap worse:

    | field, long value | overlap on main | first revision | now | | ----------------- | --------------- | -------------- | --------------- | | shrink-to-fit | 12px | 28.09px | none, 7px clear | | in InputGroup | 12px | 33px | none, 7px clear | | 220px | 12px | 31.09px | none, 7px clear | | 180px | 12px | 33px | none, 7px clear | | 140px | 12px | 33px | none, 7px clear | | escape, 140–220px | none | up to 4px | none |

    No new API and no constants. An earlier revision floored the field with a --typeahead-min-width public var defaulting to 200px, which review rightly rejected: it was a second sizing contract beside the documented Field.width prop, it was hand-derived (the empty field measures 199, so the floor overshot by 1), InputGroup cancelled it, and it could not help Tokenizer. Nothing here states a width; the lane's min-width: 0 is the opposite of a floor.

    Tokenizer is not fixed here. It shares the family promise and breaks it — 199px empty to 114.7px with one token, in the same probe — but by a different mechanism: its tokens are in flow and wrap, and its input deliberately becomes a 40px continuation lane after them, so what a wrapping multi-value field's width should be is a design question rather than this bug. Its numbers are identical before and after this change.

  • Typeahead, Tokenizer: the busy indicator is a Spinner in the field's end lane, and the input keeps its text out from under it (#5555) Three defects in one block. The indicator a search painted was <Icon icon="clock"> — a static glyph, in a family where every other input paints busy with a Spinner, and where clock otherwise means time. It was an in-flow item at the row's inline end, which is where each field independently parks its clear button, so the two landed on each other: 17×20px of overlap in Typeahead and 19×20px in Tokenizer. The overlap is visual, not functional — the clear button is positioned, so it paints above the in-flow indicator and stays clickable across the whole covered band. And the combobox never carried aria-busy, unlike every sibling input.

    The base engine now reports the busy state to the field, which paints it in the one inline-end lane it already owns beside its clear button and end content, and sets aria-busy on the input. A caller using BaseTypeahead directly is unaffected: it still renders its own visible, named "Loading" status, now a Spinner rather than the clock.

    Typeahead puts both controls in flow, as ordinary flex siblings of the input, exactly as TextInput does with its own spinner and clear button — an in-flow box takes up room, so the input cannot run under it and there is nothing to measure. Getting there meant dropping flex-wrap: wrap from its wrapper, which the shared field base does not set and TextInput does not use: this field holds at most one token, so there is no second row to wrap to, and wrapping is what made an in-flow lane impossible, since flex moves an item to a new line rather than shrinking it. Measured in Chromium: with flex-wrap restored and a token too wide to share the row, the end controls drop to a second row and a 280px field grows from 32px to 46px tall. Unwrapped, a long value ellipsizes in the token instead.

    Tokenizer's own pre-existing case of the overlap closes with it: at 280px with a token and no search running, its clear button covered 20px of the input's content box, and covers none now.

    Tokenizer keeps a measured lane, because it cannot use the in-flow shape: its lane stays pinned to the field's first row while tokens wrap below it, so it has to be out of flow, and an out-of-flow box reserves nothing. Its width is measured with offsetWidth rather than getBoundingClientRect(). The rect is in viewport space — it carries every CSS transform above the element — while the padding it feeds is in local space, so mixing them broke under any transform: measured in Chromium, scale(.5) reserved half of what was needed and put the query back under the controls by 22.83px, and scale(2) left the caret in a 202.69px gap. offsetWidth is the untransformed border-box width and reports the same number at every scale.

    The measurement reaches CSS as a custom property written to the field wrapper, never as React state, so a lane that grows or shrinks repaints without re-rendering the field. Held in state it cost a second commit every time the lane changed size — once as the spinner arrived and once as it left — which doubled the field's commits across a search for a value no JavaScript reads. The observation is shared too, through the same observeResize singleton useTruncation uses, so a page of fields costs one callback per frame rather than one observer each. The property is --_tokenizer-end-lane-width: private and component-named, like every other runtime layout var in the package, and never something a theme writes.

    The busy indicator now appears in each field's documented anatomy, delegating its theming to component:Spinner rather than gaining a target of its own — the disposition TextArea, CheckboxList and CommandPalette already use for the same part.

Performance

  • Keep Tooltip refs stable across rerenders (#5951)
  • Markdown streaming bounds four incremental-parse operations by the mutable tail: splitting, fence/boundary detection, link-definition collection, and block re-parsing no longer grow with the already-settled document. The parser contract is unchanged: each call returns a fresh, never-mutated snapshot, and replacing already-settled text still re-parses the document. Two costs intentionally remain proportional to the whole input on each call because that contract requires them — the settled-prefix comparison that detects a replaced document, and the pointer-per-block copy behind each returned snapshot. (#5515)
  • Typeahead: skip the loading cycle for synchronous bootstrap sources (#5955) BaseTypeahead now applies an array returned by SearchSource.bootstrap() immediately instead of entering and leaving the asynchronous loading state. An empty synchronous bootstrap becomes a render no-op, while synchronous entries still open normally. Switching from an in-flight search to a synchronous bootstrap also clears the superseded search's loading state. Promise-backed bootstrap sources keep the existing loading behavior.

Documentation

  • Grid, Stack, HStack, VStack, GridSpan, and StackItem: seed example content via playground defaults (and, for the two sub-components, a real parent wrapper) so the docsite properties-tab preview renders a working component instead of an empty stage. (#5892, #5893, #5894, #5898, #5899, #5900)
  • The namespaced-icon rationale and the add-a-semantic-icon intro in the icons guide, and the SideNavItem actions prop description, now use a comma and a colon in place of prose em dashes. Meaning unchanged. (#5647)
  • The Popover presentation best practice and the Banner collapsible best practice now use a straight apostrophe instead of a curly one, so the strings match the rest of the doc copy. Meaning unchanged. (#5772)
  • The Spinner CSS-variable and size descriptions, and the useTableGroupedRows description, now use colons and semicolons in place of prose em dashes. Meaning unchanged. (#5597)
  • The Stepper progress bar anatomy description now sets its nested aside in parentheses instead of paired em dashes, so the sentence about multi-segment spans reads plainly in the CLI and doc site. Meaning unchanged. (#5691)

Other Changes

  • Core's postinstall no longer hand-mirrors the setup contract. packages/core/scripts/agent-doc-state.mjs is now GENERATED byte-for-byte from the CLI's dependency-free leaf packages/cli/foundation/agent-docs/agent-doc-state.mjs, and pnpm check:setup-contract — wired into check:repo — fails the build when the two differ. (#4162) The previous guard compared two hand-edited constant lists. That caught a new agent-doc path or a new marker, and nothing else: the predicate itself, and the shouldNudge decision matrix duplicated in both postinstall scripts, could still drift and leave layer 1 and layer 2 disagreeing about "is this project set up?" with the test green. shouldNudge and the nudge string move into the contract as well, so all four things — paths, markers, predicate, decision — now have one definition and one place to edit.

    Behavior is unchanged, and verified rather than assumed: the nudge text is byte-identical, legacy <!-- XDS:START --> blocks still count as set up, all six agent-doc locations are still detected, and both scripts still exit 0 on every path including failure. Core loads its copy with a dynamic import, so a packaging mistake degrades to "no nudge" instead of throwing out of module evaluation and failing a consumer's install. check:setup-contract also fails if core stops listing the generated file in files, so it cannot go missing in the first place.

  • minSize / maxSize join defaultSize in one vocabulary: a non-negative finite number, an exact Npx, an exact N% from 0–100, Table's existing pixel(value), or percent(value, {min: pixel(value)}) / percent(value, {max: pixel(value)}) for a percentage with exactly one pixel floor or ceiling. percent() requires its options; '40%' remains the only unbounded percentage spelling. minSizePx/maxSizePx remain deprecated aliases, each an exact mutually-exclusive TypeScript union with its replacement; if untyped code supplies both, the unified prop wins and development names the ignored alias.

  • containerRef (caller-owned) changes only what a percentage is a share of: that element's content-box size on the active axis, direction selecting inline or block. Omitted, percentages keep the released one-time window.innerWidth resolution with its 1200px server fallback.

  • A percentage default resolves once into a pixel selection, applying its optional structured floor or ceiling exactly once. Percentage bounds re-resolve with their basis, apply that one pixel bound, and clamp the selection — they never rescale it. A basis change is not a user interaction: it fires no onSizeChange and persists only resolved pixels.

  • Everything else stays pixels, exactly as released: pointer, keyboard, snaps, collapse/expand, persistence, callbacks, and resize(number). resize('50%') remains a type error, and resize(NaN), resize(Infinity) or a negative now warn and keep the last legal size instead of poisoning state.

  • Invalid configuration repairs deterministically — 250px for a default, 50px for a minimum, unbounded for a maximum — identically in development and production, warning only in development. Explicit maxSize: Infinity and maxSizePx: Infinity keep the released unbounded behavior. The deprecated aliases retain their released exact atomic-string behavior for untyped callers. An inverted pair warns and the maximum wins, preserving the released clamp order.

    The structured API follows Table's existing shape rather than parsing CSS expressions: Resizable/utils is a server-safe subpath that re-exports the exact same pixel() binding and PixelWidth type as Table/utils, alongside Resizable's percent() and types. pixel(value) is the canonical structured static size; raw numbers and exact Npx remain compatible. proportional() remains Table-only because it describes sibling weight, not a literal percentage of one measured basis. CSS min() / max() strings are deliberately unsupported.

    The defect this closes: a percentage ceiling could previously only be written in CSS, and CSS stops the paint but not the state. ResizeHandle publishes the hook's size as aria-valuenow, so the separator announced a width the panel did not have — measured at 899.5 against a 434px panel. Bounds now clamp the state, so paint, persistence and ARIA describe one geometry.

    ResizeHandle also warns in development when its direction disagrees with its region's, which previously failed silently. Existing vertical panels must pass direction: 'vertical' to useResizable as well as direction="vertical" to the handle.

    The container basis follows the ref, not the element it first pointed at: replacing the element behind the same containerRef re-resolves against the replacement, and the element left behind is unobserved. A container that is not laid out yet — unmounted, display:none, detached — measures 0, which is not a measurement: percentages hold the documented temporary 1200px basis until it is real, and nothing is written to autoSaveId storage from it. Once the first real basis resolves, the default is committed as a pixel selection with its initial clamp included; a 321px default clamped to 200px therefore stays 200px when the container later grows instead of reviving the raw default.

    A gesture that is cancelled rather than completed — pointercancel, a lost pointer capture, a handle unmounted mid-drag — releases the basis it froze through a new optional _onResizeCancel on ResizableProps. It is not a resize end (a cancelled drag deliberately signals none, per #5297), but it is the end of the gesture. _onResizeCancel and _direction are both optional: ResizableProps is exported, so an object literal that satisfied the released type still compiles.

    Not in scope, per the spec: SideNav's simplified defaultWidth/minWidth/maxWidth stays pixel-only.

    A pixel-only configuration keeps its single render pass even when a containerRef is supplied. With no percentage anywhere there is no basis to observe or ref identity to follow, so the pixel selection is made at mount; only a basis-dependent configuration with a supplied container defers until that measurement exists.

@astryxdesign/cli

New Components

  • Add popover, bottom-sheet, and adaptive presentation options to Selector and MultiSelector, with docsite examples for both bottom-sheet variants. (#5395)
  • Reuse Neutral-owned local tokens for semantic status fills across badges, status dots, step indicators, and progress bars. (#5854)
  • Add Neutral's reproducible, theme-owned OKLCH palette without changing its runtime token mappings. The request, receipt, generated result, and CLI template artifacts are committed together for review. (#5987)
  • Add the opt-in theme-local token contract for maintained theme families. (#5844)

New Features

  • Add structured accessibility requirements and theme coverage support to component documentation. (#5713)

  • Add the checkout wizard page template. (#5660)

  • CLI: record every command run and hand it to a function you supply. (#4812)

    // astryx.config.mjs
    export default {
      debug: event => appendFileSync('runs.ndjson', JSON.stringify(event) + '\n'),
    };

    That is the whole feature. Setting debug opts in; the function receives one DebugEvent per invocation and decides what happens to it. The CLI stores nothing.

    Each event carries the command, its arguments and flags (with their Commander source, so you can tell a typed flag from a default), the outcome, exit code, duration, error code, a coarse environment snapshot including which coding agent invoked the CLI, and — under output — everything the command printed to stdout and stderr. That last part is the answer the user actually got, which is what makes a record useful for improving the output rather than just counting invocations. Streams are captured separately with their true byte counts, and truncated past 32KB per stream so a command that prints a whole file does not dominate the record. Coverage is the point: handled errors, parse errors, --help, rejected invocations, uncaught throws, and Ctrl-C all report. The event is delivered from a process.on('exit') listener because the CLI's error path exits synchronously — anything hooked to normal completion would report successes and almost no failures — and the handler is loaded before parsing, because parse errors and --help short-circuit before any hook runs.

    event is a published contract: DebugEvent is exported from @astryxdesign/cli/debug with a sealed zod validator, parseDebugEvent, drift-locked to the type so the recorder cannot add a field without publishing it. schemaVersion is a literal, so widening it turns every consumer's branch into a compile error rather than a silent misread.

    The handler runs synchronously at exit — a returned promise is never awaited, so network delivery from inside it will not work; write a file or spawn a detached child. It receives a copy, so a handler that throws, or mutates what it was given, can neither fail the command nor affect anything else. Follow-up hardening keeps a handler from replacing the command's exit code and routes handler writes away from stdout so a --json envelope stays valid. (#5929)

    Nothing changes for a project that has not set debug. Startup is unmoved: the environment probe is deferred to delivery rather than run in begin, because its first Intl call initialises ICU and that alone was ~9% of the CLI's startup for everyone. Nor does the config run: Project.load evaluates the config module and loads its integrations, which most commands never did, so the file is read as text first and only loaded when the word debug appears in it. Measured across eight commands, no command evaluates a config that did not already.

    Values are scrubbed before delivery: home paths, absolute paths inside stack frames, email addresses, URL credentials, credential-shaped strings, and the value half of a sensitive assignment wherever it appears — including where an error message, a stack frame and the captured stderr all quote the flag that was rejected. Sensitive names are matched with - and _ stripped, so --api-key, --api_key and --apiKey are one rule; key, pat and pw are matched whole so they do not take --keyboard and --path with them. argv is scrubbed pairwise, so --token hunter2 loses its value the way --token=hunter2 does. Oversized values are clamped.

    Hardened against three adversarial chaos runs and an independent review, each finding mutation-tested before its fix landed: a __proto__ key silently reparenting the record that carried it, one oversized value discarding the whole event, an exit that bypassed cliError being indistinguishable from a classified failure, a signal-terminated run leaving no record at all, a sensitive --flag=value scrubbed in argv but written back out in full through the error message and captured stderr that quote it, absolute paths surviving inside stack frames — where nothing puts whitespace in front of them — and taking the machine's username with them, a graceful Ctrl-C recorded as a failure with an exit code the process never returned, --api-key and --token value reaching a handler intact, and the two startup costs above.

    One change reaches beyond this feature: installJsonShim now shims commands as they join the command tree rather than in a single walk at startup, so a command registered later can no longer silently fall out of the --json contract.

  • CLI: astryx init --json now works. It emits the install receipt as a standard envelope — init.run with the mode, the features that ran, the agent-doc files written, any soft docsError, and the template outcome, or init.remove for --remove-agents. Human output is suppressed so stdout carries only the envelope, and the exit code is unchanged from human mode. (#4812) init was the last side-effecting command still refused by the --json gate. That gate existed to stop a command writing half a project and only then reporting that --json was unsupported; since init() already returned a typed receipt, the fix was to emit it rather than to keep refusing. theme and layout remain off the allowlist, but both are command groups with no output of their own.

  • Add result and agent-session context to DebugEvent (#5971)

  • Add the dialog wizard page template. (#5798)

  • Let themes add typed Heading visual roles with a safe semantic-level fallback when the owning theme styles are unavailable. (#6026)

  • Add the form wizard page template. (#5664)

  • Add the inline wizard page template. (#5797)

  • An integration can now supply a debug handler, so installing it turns on its debug logs with no change to the app. Export debug from astryx.integration.*; the app's own debug still runs, both get every event, and a handler that throws cannot affect the command. Opt out with {"astryx": {"inheritDebug": false}}. (#5998)

  • Mute the low-tone edge of Neutral's dark chromatic palette while preserving its light and neutral ramps. (#6069)

  • Rebuild the table-page template as a searchable, filterable, sortable table pattern with filter-aware totals, row detail, a scrolling document masthead, and guidance organized around narrowing, sorting, and communicating filtered state. (#5865)

  • TabList: add an isFullBleed prop so a tab bar can bleed out to its container's inline content edges instead of requiring hand-written negative-margin CSS (#2622). Like Divider's isFullBleed, it cancels the nearest padded Layout container's --container-padding-inline-* custom properties with negative margins; the inner strip pads back by the amount the bleed exceeds a tab stop's own padding so edge labels remain aligned to the content inset. It is inline-only: TabList owns the inline full bleed, and the container owns the block-end dock. For that, LayoutHeader gains a paddingBlockEnd per-edge override in Section's existing spelling — paddingBlockEnd={0} docks the header's last child on its bottom edge so a tab strip's underline meets hasDivider at any header padding. The detail-page template now uses both props, aligns its ghost panel toggle with the container inset, and no longer carries any hand-written tab-row CSS.

  • Add the vertical wizard page template. (#5672)

  • Add the work-item-detail page template for task, ticket, issue, story, bug, card, and request detail surfaces. It includes responsive main-content and details-rail layouts, editable metadata, subtasks, attachments, comments, activity, and a narrow-viewport details dialog. (#5926)

Fixes

  • build: a page that matched one word of the query is no longer offered as a direct match. A page template's keywords include every component its source renders, so build "actionable warning banner" returned login, contact-form and documentation-design at 95 apiece — an exact keyword hit on "banner" alone, plus the coverage garnish, landing exactly on the direct-match threshold. Three pages that are not warnings, presented as the page to start from. Coverage now gates the pages group rather than garnishing its score. Score alone could not carry the gate, so scoreQuery now reports the coverage it already computed: matchedTerms / queryTerms on every search result, with a whole-phrase hit reporting full coverage. A single strong hit and a broad weak one land on the same score, so a caller cannot tell "one of three concepts" from "three of three" without it.

    Rebased onto current main, which landed integration search (#5259) and the scorer-level false-direct-match fix (#5614) while this was open. Both are main's implementations, untouched here — this branch no longer rewrites gatherComponents, so the two regressions that rewrite caused are gone with it: an integration result reports its own package again, and a broken config no longer turns a Core button search into an empty success.

    The other two pieces this branch used to carry now ship separately, as asked: the guidance-tier indexing in #5937 and the thin-kit hint in #5938.

  • A thin build kit now says what to try instead of looking empty. build "quantum flux capacitor telemetry" returned one incidental component and the always-on frame list, and said nothing else. An agent reading that does not conclude its wording was wrong — it concludes the package has nothing and falls back on its own memory of what Astryx contains, which is the failure build exists to prevent.

    Below three offerable results the kit carries a hint naming the two commands that browse rather than search, and saying plainly that this is keyword matching, not semantic. The threshold counts what SURVIVED the score floors, not what search returned: hasResults is already true for a query that matched things and then filtered them all out, and that is the case most likely to be misread.

    hint is structured — {reason, commands} with bare subcommands — not a sentence with commands baked into it. The API cannot know how a project invokes the CLI, and a hardcoded astryx component --list does not resolve in a pnpm workspace, where every other command in this output renders as pnpm exec astryx. The renderer formats them through formatCliCommand, so they are runnable as printed, and a JSON caller gets the parts rather than prose to re-parse.

    hint is present only when it applies, so a healthy kit is byte-identical to before. The CLI renders it last, as a FEW MATCHES section listed in the legend's section order, so it is the line the reader leaves with.

    Split out of #5320 at review request. Public response doc (build.doc.mjs) and the BuildKitResponse type both updated.

  • The shared CLI blog adapter (blog.list, blog.detail) cleared its 15-second abort timer as soon as fetch returned response headers, leaving the later body read unbounded in time, and buffered the entire response before checking the 5 MB size limit, so the limit didn't actually cap how much was read into memory. (#5286) The abort timer now stays active through body consumption. The body is read as a stream where available, checking decoded size after each chunk and aborting the read as soon as it exceeds the limit, instead of buffering the full response first.

  • CLI: recorded runs no longer carry a raw agent session id, and the environment snapshot is scrubbed like every other value. (#6051) A DebugEvent claimed redacted: true while env had never been through the scrubbing pass, and it stored the raw agentSessionId beside its hash. A session id follows one person across every run they make, and a handler may forward these records anywhere — so the record shipped a stable identifier, and an agent name pasted in from the environment went out verbatim, under a flag that said neither had.

    The contract is now explicit on DebugEventEnv: no identity, attribution only from positive evidence, and free text scrubbed. env.agentSessionId is always null — join runs on env.agentSessionIdHash, which is what the raw value was for. Everything the CLI derives itself (platform, CI provider, locale, the hash) is still recorded verbatim, because a scrubbed snapshot is not worth keeping. redacted is set only on the sealed copy, after every pass has actually run.

    DebugSchemaVersion widens to 1 | 2 and the CLI emits 2, so code that switches on it is forced to handle both rather than silently reading a field that no longer means what it did. parseDebugEvent is version-aware to match: a v1 record may carry the raw id, a v2 record may not and is rejected if it does.

    Not a breaking change: debug and the whole DebugEvent surface are unreleased — they land in this same release — so no published consumer ever saw the raw identifier.

  • A manifest key this CLI does not know no longer discards the whole integration. astryx.integration.* was parsed with a strict schema, so one unrecognized field failed the parse — and an integration whose manifest fails to parse contributes nothing, taking its components, templates and codemods down with it. Since an integration is published once and installed against many CLI versions, a field added by a newer CLI reached every older consumer as total, silent loss of that package (#5119). Unknown fields are now ignored with an unknown_manifest_key warning naming them, and the rest of the manifest still applies; a known field of the wrong type is still an error. (#5311 follow-up)

  • CLI: a stray lockfile no longer overrides the packageManager your project declares. (#6051) One yarn install inside a pnpm project leaves a yarn.lock behind forever. A single lockfile used to outrank the packageManager field, so the CLI answered "yarn" for a project that says pnpm — and printed yarn astryx … in every command it suggested, including the invocation line written into agent docs, where agents copy it. astryx doctor called that setup healthy.

    The declared packageManager field now decides, whatever lockfiles sit beside it. The documented fallbacks are unchanged: with nothing declared, a single lockfile still answers, a committed pnpm-workspace.yaml / .yarnrc.yml / bunfig.toml still breaks a multi-lockfile tie, an unbroken tie still resolves to the neutral npx form with a doctor FAIL, and the runner is still consulted only when the whole walk found nothing.

    astryx doctor now WARNs when a lockfile contradicts the declaration, names the file, and says what to delete — instead of reporting the project as fine.

  • astryx search (and astryx build, which shares its ranking) never surfaced a component whose exact multi-word keyword phrase was searched, if enough other unrelated candidates happened to each contain one of the query's individual words. Searching "table of contents" returned no results for Outline, even though Outline.doc.mjs declares 'table of contents' verbatim as a keyword, because Table-related templates each matched table and contents separately and their combined per-word score outranked Outline's single exact match. A query that exactly matches a candidate's declared keyword (or name) verbatim is now promoted to a top-tier score, so it always outranks a candidate that only coincidentally contains several of the query's individual words. Single-word queries and queries that don't exactly match a keyword are unaffected.

    Follow-ups #6001 and #5994 preserve the coverage counts needed by build ranking while keeping them out of the public search result shape.

  • CLI: search and build now report how many results MATCHED, not how many were returned. (#6051) matchCount on a build.kit envelope, and output.resultCount on a recorded run, were both the length of the list after --limit had cut it. A query matching two hundred things and one matching exactly twenty filed the same number, so nothing downstream could tell a capped answer from a complete one — and a thin kit read as "the package has nothing" when it was really "the cap hid the rest".

    search --json now carries matchCount alongside results, and the text view says Results for "x" (2 of 57) when the list was cut short. The payloads themselves are unchanged: results is still bounded by --limit, and the kit still surfaces at most 3 pages, 5 blocks, and 6 components.

  • Rename five dashboard page template catalog slugs to reusable pattern names, per the naming convention in Contributing Templates: dashboard-datadashboard-comparison, dashboard-executive-summarydashboard-scorecard, dashboard-portfoliodashboard-composition, dashboard-project-statusdashboard-progress, and dashboard-service-monitoringdashboard-alert-rail. Each old slug named the data or task rather than the reusable pattern, so it under-served neighbouring requests: the composition-over-time shape is not specific to portfolios, and the alert-rail shape is not specific to service monitoring. The old slugs no longer resolve because catalog lookup is exact-match; use the new current-catalog values above. The template command and machine-readable schema are unchanged. (#5927) Two categories move with their slugs: dashboard-comparison takes Dashboard - Comparison (it previously shared Dashboard - Analytics verbatim with the dashboard template, so neither owned the keyword) and dashboard-scorecard takes Dashboard - Scorecard. Both values are added to the TemplateCategory union; the superseded values stay reserved. Domain vocabulary — portfolio, holdings, monitoring, uptime, executive summary — is untouched in each description, which is where retrieval actually reads it from.

    Also fixes a typo in the scorecard template's name field ("Executive Summary Dashoard").

  • Deduplicate parent-owned theming targets in CLI discovery while preserving each child component's direct documentation. (#5767)

  • Preserve anatomy when loading localized component docs directly (#5761) The validated component-doc loader now applies the same full-overlay fallback as the CLI loader, so omitted localized anatomy inherits the canonical structure while explicit localized anatomy still wins.

  • Package-manager detection: don't let a stray lockfile decide (#5301) detectPackageManager checked lockfiles in a fixed order and returned the first hit, so a directory holding more than one lockfile was resolved by array position. yarn.lock is first in that array, which means a single yarn install inside a pnpm project silently switches the CLI's answer to yarn — permanently, because the stray lockfile stays on disk.

    Every command the CLI prints is then wrong, including the invocation line written into the agent-docs block, which agents copy verbatim into their own runs.

    An explicit packageManager declaration is authoritative even when the project also contains one or several lockfiles. Without a declaration, a single lockfile remains decisive. When several lockfiles sit in one directory, the tie is broken only from other evidence the project owns: a committed package-manager config file (pnpm-workspace.yaml, .yarnrc.yml, bunfig.toml). A stray install drops a lockfile; it writes none of those.

    The runner (npm_config_user_agent) deliberately does NOT break that tie. An agent handed the wrong yarn astryx line runs the CLI through yarn, so the runner agrees with the mistake and regenerating agent docs writes the wrong line again — the failure reproduces itself. The same holds for an installed binary invoked from that shell. Both now have regressions that start from the wrong line.

    When nothing project-owned decides it, the CLI does not guess. detectPackageManager returns the neutral npx, which is correct under every package manager, and the new explainPackageManager reports ambiguous with the tied candidates. astryx doctor turns that into a FAIL naming the directory and the fix — add a packageManager field, or delete the lockfile that does not belong. It is the refusal findConfigPath already makes for coexisting config files.

  • LayoutPanel: add playground wrapper and default children for docsite preview (#5919) Prevents the properties-tab preview on the docsite from rendering an empty stage by wrapping LayoutPanel inside a Layout scaffold with representative panel content in start slot.

  • Preserve canonical anatomy in localized component docs (#5753) Localized component docs now inherit canonical anatomy when they omit it, while explicit localized anatomy still takes precedence.

  • Make the table-filter page template usable on narrow and touch surfaces: View options, the detail panel, the saved-view dialogs and the filter overflow adapt to bottom sheets, the filter and saved-view rows hold one line, and column freezing is dropped where there is too little width to scroll the rest. (#5829)

  • Correct Neutral Banner interaction tints so light mode uses translucent light overlays and dark mode uses translucent dark overlays. (#5936)

  • Use palette-backed red interaction overlays for Neutral destructive buttons, solid dark-palette tone-25 backgrounds (tone 20 for gray), and calmer dark-mode text colors. Use a palette-backed muted blue tint for dark info banners while preserving the existing light-mode non-semantic color mappings. (#6049)

  • Give Neutral segmented controls a roomier inset while preserving their outside height. (#5851)

  • Rename built-in syntax theme identifiers. (#5847)

  • Remap Neutral's semantic, syntax, and categorical color tokens to the reviewed theme-owned palette through named stop references. Keep the maintained CLI template synchronized. (#6034)

  • Keep query-coverage metadata internal to build ranking while preserving it for promoted exact-phrase matches. (#5994)

  • search indexes a component's usage guidance, one tier below its description. (#5937) A component's best practices are where the reader's vocabulary lives. Banner describes itself as "a persistent message"; only its guidance says "caution", "problems", "form errors". None of those words found it, because guidance was never read — 97 core components ship guidance, and all of it was invisible to search.

    Measured on the real registry, before and after: caution, problems, sources and attention each now return the component whose guidance defines them, and each returned nothing relevant before.

    Guidance scores 45, below description's 50, so a component that IS the answer still outranks one whose advice merely mentions the term — the ordering that put Toast behind Card, Dialog and Item on "notification".

    It sits deliberately BELOW MIN_TOKEN_SCORE, so it never counts as a matched concept in a multi-word query. That is not a detail: letting it count was measured moving nested menu from SideNav to List, and explain why a field is required from Field to TextInput — a component whose guidance happens to mention the other word displacing the one that is the answer. Breadth is not relevance, the same reason weakKeywords are capped. With the floor left at 50, a 28-query sweep shows zero top-result changes and zero regressions, while the single-word gains above are kept.

  • Table inbox replies now preserve an unsent body only for the same conversation, preventing text from following changed recipients. (#5934)

Documentation

  • The namespaced-icon rationale and the add-a-semantic-icon intro in the icons guide, and the SideNavItem actions prop description, now use a comma and a colon in place of prose em dashes. Meaning unchanged. (#5647)
  • The description prose of 12 page templates now uses parentheses, commas, and colons in place of em dashes. These strings feed the CLI template list and the doc site, so plain punctuation reads better there. Meaning unchanged. (#5679)

Other Changes

  • Core's postinstall no longer hand-mirrors the setup contract. packages/core/scripts/agent-doc-state.mjs is now GENERATED byte-for-byte from the CLI's dependency-free leaf packages/cli/foundation/agent-docs/agent-doc-state.mjs, and pnpm check:setup-contract — wired into check:repo — fails the build when the two differ. (#4162) The previous guard compared two hand-edited constant lists. That caught a new agent-doc path or a new marker, and nothing else: the predicate itself, and the shouldNudge decision matrix duplicated in both postinstall scripts, could still drift and leave layer 1 and layer 2 disagreeing about "is this project set up?" with the test green. shouldNudge and the nudge string move into the contract as well, so all four things — paths, markers, predicate, decision — now have one definition and one place to edit.

    Behavior is unchanged, and verified rather than assumed: the nudge text is byte-identical, legacy <!-- XDS:START --> blocks still count as set up, all six agent-doc locations are still detected, and both scripts still exit 0 on every path including failure. Core loads its copy with a dynamic import, so a packaging mistake degrades to "no nudge" instead of throwing out of module evaluation and failing a consumer's install. check:setup-contract also fails if core stops listing the generated file in files, so it cannot go missing in the first place.

@astryxdesign/build

Fixes

  • withAstryx() now resolves an app's own @astryxdesign/* imports to the packages' source entries. The scoped webpack rule only governs requests issued from inside node_modules, so app code resolved the library through default to dist while PostCSS compiled it from source — the two emit disjoint class names and the app rendered unstyled without erroring. (#5932)

@astryxdesign/theme-butter

Fixes

  • Rename built-in syntax theme identifiers. (#5847)

@astryxdesign/theme-chocolate

Fixes

  • Rename built-in syntax theme identifiers. (#5847)

@astryxdesign/theme-gothic

Fixes

  • Rename built-in syntax theme identifiers. (#5847)

@astryxdesign/theme-matcha

Fixes

  • Rename built-in syntax theme identifiers. (#5847)

@astryxdesign/theme-neutral

New Components

  • Reuse Neutral-owned local tokens for semantic status fills across badges, status dots, step indicators, and progress bars. (#5854)
  • Add Neutral's reproducible, theme-owned OKLCH palette without changing its runtime token mappings. The request, receipt, generated result, and CLI template artifacts are committed together for review. (#5987)

New Features

  • Mute the low-tone edge of Neutral's dark chromatic palette while preserving its light and neutral ramps. (#6069)

Fixes

  • Correct Neutral Banner interaction tints so light mode uses translucent light overlays and dark mode uses translucent dark overlays. (#5936)
  • Use palette-backed red interaction overlays for Neutral destructive buttons, solid dark-palette tone-25 backgrounds (tone 20 for gray), and calmer dark-mode text colors. Use a palette-backed muted blue tint for dark info banners while preserving the existing light-mode non-semantic color mappings. (#6049)
  • Give Neutral segmented controls a roomier inset while preserving their outside height. (#5851)
  • Rename built-in syntax theme identifiers. (#5847)
  • Remap Neutral's semantic, syntax, and categorical color tokens to the reviewed theme-owned palette through named stop references. Keep the maintained CLI template synchronized. (#6034)

@astryxdesign/theme-stone

Fixes

  • Rename built-in syntax theme identifiers. (#5847)

@astryxdesign/theme-y2k

Fixes

  • Rename built-in syntax theme identifiers. (#5847)

Contributors

Thanks to everyone who contributed to this release:

@cixzhang @ernestt @freddymeta @Geervan @harjothkhara @HelloOjasMutreja @imdreamrunner @jiunshinn @josephfarina @kentonquatman @Kyujenius @Lee-Dongwook @ManoharPaturi @mattandryc @nynexman4464 @PRIEYAN @rubyycheung @trakshan-mishra @yyq1025

Full Changelog: https://github.com/facebook/astryx/compare/v0.5.2...v0.5.3

3 hours ago
wangEditor-next

v6.4.0

What's Changed

  • [basic-modules] Allow table cells to preserve and accept video and code-block content while rejecting unsupported custom blocks and nested tables.
  • [core] Allow table cells to contain block descendants such as paragraphs and lists while preserving legacy text-only cells through normalization and HTML round trips. Core now exposes HTML-to-content conversion and a composable initial-content transform hook for modules that need boundary migrations.
Package versions
Package Version Source
@wangeditor-next/basic-modules 6.4.0 Source (tar.gz)
@wangeditor-next/code-highlight 6.4.0 Source (tar.gz)
@wangeditor-next/core 6.4.0 Source (tar.gz)
@wangeditor-next/editor 6.4.0 Source (tar.gz)
@wangeditor-next/editor-for-react 6.4.0 Source (tar.gz)
@wangeditor-next/editor-for-vue 6.4.0 Source (tar.gz)
@wangeditor-next/editor-for-vue2 6.4.0 Source (tar.gz)
@wangeditor-next/list-module 6.4.0 Source (tar.gz)
@wangeditor-next/plugin-attachment 6.4.0 Source (tar.gz)
@wangeditor-next/plugin-ctrl-enter 6.4.0 Source (tar.gz)
@wangeditor-next/plugin-float-image 6.4.0 Source (tar.gz)
@wangeditor-next/plugin-formula 6.4.0 Source (tar.gz)
@wangeditor-next/plugin-link-card 6.4.0 Source (tar.gz)
@wangeditor-next/plugin-markdown 6.4.0 Source (tar.gz)
@wangeditor-next/plugin-mention 6.4.0 Source (tar.gz)
@wangeditor-next/table-module 6.4.0 Source (tar.gz)
@wangeditor-next/upload-image-module 6.4.0 Source (tar.gz)
@wangeditor-next/video-module 6.4.0 Source (tar.gz)
@wangeditor-next/yjs 6.4.0 Source (tar.gz)
@wangeditor-next/yjs-for-react 6.4.0 Source (tar.gz)
@wangeditor-next/yjs-for-vue 6.4.0 Source (tar.gz)
5 hours ago
viewerjs

v1.13.0

See CHANGELOG.md for details.

6 hours ago
drawio
14 hours ago
hooks

v3.10.0

ahooks 3.10.0

中文

新功能与增强

  • useAsyncEffect 支持同步返回清理函数,在依赖变化和卸载时由 React 执行清理。注意:这不支持 async () => cleanup,异步函数返回的清理函数不在本次支持范围内。#2958
  • useLocalStorageState 新增 getInitialValueInEffect,允许挂载后再读取存储,以避免 SSR hydration 不一致。#2906
  • useSetState 支持省略初始状态,返回状态类型为 Partial<T>#2905
  • useAntdTable 支持表单实例的 validateFieldsReturnFormatValue,兼容 ProForm 的 transform

修复

  • useRequest 修复被取消或被后续调用覆盖时的异步调用悬挂问题,并提供 CancelledError / isCancelledError#2951
  • useRequest 修复节流场景下 runAsync 不生效,以及 ready=false 时防抖前沿调用影响后续请求的问题。#2899#2946
  • useRequest 修复默认参数场景的类型推导,并在删除缓存时清理相关定时器。#2907#2909
  • useInfiniteScroll 修复首次 loadMore 期间闭包读取旧数据的问题。#2896
  • useKeyPress 修复 exactMatch 下修饰键的 keyup 事件匹配。#2944
  • useLongPress 修复 Pointer Events 支持。#2947
  • useTheme 为旧版 Safari 提供媒体查询监听回退。#2916
  • 修复 useEventListener 显式泛型与 ref target 的兼容性,并保留非空 ref 的返回类型。#2903#2956
  • 移除已废弃的 intersection-observer 依赖。#2904

升级注意

runAsync / refreshAsync 被取消或被新调用覆盖时,会以 CancelledError 拒绝,而不再一直保持 pending。直接调用这些异步 API 的代码应捕获异常,并用 isCancelledError 区分取消与业务错误;run / refresh 会内部处理取消。

English

Features and enhancements

  • Support synchronous cleanup functions in useAsyncEffect, with React managing cleanup on dependency changes and unmount. Returning cleanup from an async callback remains unsupported. #2958
  • Add getInitialValueInEffect to useLocalStorageState to defer storage reads until after mount and avoid SSR hydration mismatches. #2906
  • Allow useSetState without an initial state, returning Partial<T>. #2905
  • Support ProForm's transform in useAntdTable through validateFieldsReturnFormatValue.

Fixes

  • Settle cancelled or superseded useRequest asynchronous calls with CancelledError; export CancelledError and isCancelledError. #2951
  • Fix throttled runAsync calls and debounce leading behavior while ready=false. #2899, #2946
  • Fix default-parameter type inference and clear timers when request cache entries are deleted. #2907, #2909
  • Fix stale data captured during the initial loadMore in useInfiniteScroll. #2896
  • Fix modifier-key keyup matching with exactMatch in useKeyPress. #2944
  • Fix Pointer Events support in useLongPress. #2947
  • Fall back to legacy media query listeners in useTheme for older Safari versions. #2916
  • Fix explicit generic and ref target compatibility in useEventListener, and preserve non-nullable ref return types. #2903, #2956
  • Remove the deprecated intersection-observer dependency. #2904

Upgrade note

Cancelled or superseded runAsync / refreshAsync calls now reject with CancelledError instead of remaining pending. Catch errors when using these APIs directly and use isCancelledError to distinguish cancellation from service errors. run / refresh handle cancellation internally.

Full changelog: https://github.com/alibaba/hooks/compare/v3.9.7...v3.10.0

16 hours ago
zip.js

v2.11.2

What's Changed in v2.11.2

Bug fixes

  • Concurrent calls to ZipWriter#add() compress concurrently again. Since v2.10.0 every entry of a batch took the path that writes directly into the zip file, and each of them then waited for the previous entry to be written, so the batch was compressed one entry at a time. The writer now records that choice in the same step that makes it, so the first entry is written directly into the file and the others are buffered, as before. Measured on 8 entries of 8 MB with the native CompressionStream, the same batch of concurrent add() calls goes from 2211 ms to 607 ms. The archive is unchanged, only the scheduling was wrong
  • A Reader that declares its size and always returns the number of bytes it is asked for no longer makes ZipWriter#add() read it forever. The writer did not pass that size when it built the readable stream, so the stream ended only when the reader returned an empty array. The stream also advances by the number of bytes actually returned instead of by the requested chunk size, so a reader that returns a short chunk in the middle of the data no longer skips the rest of the chunk

Documentation

  • The keepOrder option no longer states that concurrent calls to ZipWriter#add() compress one entry at a time and need bufferedWrite to overlap. That described the bug above, not the intended behavior

Benchmarks

  • The benchmarks report the size each library produced next to every time, and a new bench-codecs.js compares the codecs alone, sorted by output size. A compression level is not a unit shared between libraries, so a table matched on level reads a difference in compression ratio as a difference in speed
16 hours ago
next.js

v16.4.0-canary.19

Misc Changes

  • fix: route info segment overrides not updating in dev overlay: #96968

Credits

Huge thanks to @mezotv for helping!

23 hours ago
KaTeX

v0.18.6

0.18.6 (2026-09-05)

Bug Fixes

  • array: preserve tags on empty final rows (#4277) (02d552c)
1 days ago
tabler

Tabler v1.5.0

Tabler 1.5

Tabler 1.5 is out. It's the biggest release we've done, with more than 250 changes across the framework, the demo pages and the docs. The sidebar folds now. Cards can have gradients, backgrounds can have patterns, charts got seven new types, and the demo comes with two new dashboards. Bootstrap also moved into the Tabler source tree, and that's where a lot of the smaller changes in this release come from.

Folded sidebar

The vertical sidebar can fold down to a narrow strip of icons with navbar-folded. With navbar-folded-hover it stays folded and opens when you hover over it. Submenus open as flyouts next to the icon, and there's a pin toggle if you want to keep the sidebar open.

Two smaller things came with it. Section titles in the sidebar (nav-section-title) turn into short separators while it's folded, so you don't lose the grouping. And there's a new navbar-footer zone for the user block at the bottom. The navigation scrolls between the pinned brand and the pinned footer.

The sidebar is also light and 16rem wide by default now.

Layout settings

The theme panel does more than light and dark now. It also switches the layout, through four attributes you can set on <html> yourself: data-bs-layout for the container width (default, fluid, boxed), data-bs-navbar-position for whether the page shows the vertical or the horizontal navigation, data-bs-navbar for a sticky top bar, and data-bs-navbar-theme for coloring the navigation dark or primary. The folded sidebar has its own data-bs-sidebar.

The panel itself was redesigned around them, with an illustrated intro, color scheme presets and thumbnail tiles for most settings. A Customize entry in the sidebar footer and in the navbar opens it.

Gradient cards

.card-gradient gives a card a gradient background. There are variants for each theme color, modifiers for the direction, and an animated version.

The gradient stops are plain utilities, so you can use them anywhere else too. .bg-gradient-from-*, -via-* and -to-* now also take transparent and -inverted.

There's a new Card gradients page in the demo with all the combinations.

Background patterns

Backgrounds can have a pattern now. Dots, grids, stripes and a few more, through a set of .bg-pattern-* utilities.

You can also control how strong the pattern is. --tblr-pattern-opacity-factor scales the opacity globally, and .bg-pattern-opacity-* sets it per element.

New chart types

Seven chart types joined the demo and the docs: radar, polar area, treemap, timeline, box plot, bubble and funnel. There's also a new Advanced charts page for the harder cases, with two y-axes, annotations, a zoom brush and synced chart groups.

Chart colors aren't hardcoded per chart any more. There's a --tblr-chart-1 through --tblr-chart-5 palette wired into the ApexCharts theme tokens, so charts generally follow the current color mode on their own. ApexCharts itself went from 3.54.1 to 7.0.0.

New dashboard pages

Two dashboards joined the demo. One is a CRM dashboard built from reusable cards, the other a crypto dashboard with a portfolio overview, market data and order history.

There's also an onboarding page, a task list and a pay page, plus four new modals: change password, confirm delete, edit profile and new task.

Upgrading

Bootstrap 5.3.8 now lives in the Tabler source tree as Sass modules and TypeScript components, so Tabler doesn't depend on the Bootstrap package any more. A few other things changed along the way:

  • The npm package ships TypeScript declarations, so import { Modal } from "@tabler/core" is typed.
  • The default color mode is auto and follows the system setting.
  • Tabler doesn't bundle a web font any more. The default stack is the system one, so nothing gets downloaded.
  • Components accept data-tblr-* attributes next to data-bs-*.
  • The supported browsers are now Chrome 123, Firefox 128 and Safari 17.5, which is what the CSS actually needs.
  • The Turbo integration is gone, along with a number of unused Sass variables.

Every breaking change is in the Upgrade to 1.5 guide, with a before and after example for each one.

Core changes

  • 9ea657b: Added .text-gray-50 through .text-gray-950 utility classes alongside the existing .bg-gray-* utilities.
  • 100a37b: Added background pattern utilities and documentation, including updated preview demos.
  • 9d5c83f: Added Bootstrap 5.3.8 to the core source tree, with SCSS as modules and components converted to TypeScript.
  • e1ecd39: Updated the supported browser baseline to what the CSS needs: Chrome 123, Firefox 128, Safari 17.5.
  • 9c5d729: Added .btn-ghost button variant with transparent background and hover effects.
  • ec94693: Added .card-gradient component with gradient variants, direction modifiers, and animated backgrounds.
  • 324b0fb: Added --tblr-card-header-bg and --tblr-card-footer-bg variables so both backgrounds can be overridden independently.
  • 09d419a: Added a --tblr-chart-1--tblr-chart-5 palette and wired ApexCharts theme tokens, so every chart follows the color mode.
  • d2c1271: Added TypeScript declarations to the npm package, so import { Modal } from "@tabler/core" is typed.
  • 9d5c83f: Added support for data-tblr-* attributes alongside data-bs-* for dropdown and other components.
  • bf9e7ad: Added Driver.js to libs.json and dist/libs for product tours.
  • bf9e7ad: Added --tblr-dropdown-item-gap, --tblr-dropdown-item-icon-size and --tblr-dropdown-item-icon-color to .dropdown-menu.
  • 4f6e99b: Added a folded sidebar (navbar-folded, navbar-folded-hover) with flyout submenus, a pin toggle and a light 16rem sidebar by default.
  • 70f996b: Added the .font-sans-serif, .font-serif and .font-comic utilities next to .font-monospace.
  • d2c1271: Added .bg-gradient-{from,via,to}-transparent and -inverted gradient stops, which the gradient docs already described.
  • 5baf073: Added 74 new payment provider icons imported from tabler-payments and removed 12 outdated providers like dotpay and solo.
  • 5e119d4: Added bg-blur utility and increased container-tight width for layout flexibility.
  • 70f996b: Added the data-bs-layout, data-bs-navbar-position, data-bs-navbar and data-bs-navbar-theme layout settings.
  • 0c79963: Added media-print mixin and print styles to hide interactive components during printing.
  • 48dbd1e: Updated the core build system from Rollup to Vite, with identical UMD and ESM output.
  • 4ce08ca: Updated the navbar-side component and reorganized its apps, language, notifications and user sections.
  • 70f996b: Added .navbar-side support in the vertical navbar: a wrapper for a trailing .navbar-nav group pinned to the bottom of the menu.
  • f11ece4: Added --tblr-pattern-opacity-factor and .bg-pattern-opacity-* utilities for background patterns.
  • cad8eb8: Removed the $prefix Sass variable; the --tblr- prefix is now applied at build time by PostCSS.
  • 9c5d729: Added Progress Background component with text labels and value display.
  • 9c5d729: Added .progress-lg and .progress-xl size variants for the progress component.
  • 9c5d729: Added Progress Steps component for step-by-step navigation indicators.
  • bea97f8: Removed @hotwired/turbo integration, including .turbo-progress-bar styles and the Turbo loader preview demo.
  • 8962710: Removed unused SCSS !default variables, which now raise a Sass error when set via @use ... with (...).
  • 9820d11: Updated core SCSS to the Sass module system with @use and @forward.
  • 4f6e99b: Added nav-section-title group labels to the vertical sidebar, shown as short separators in the folded state.
  • 4f6e99b: Added a navbar-footer sidebar zone with a user block; the sidebar nav scrolls between the pinned brand and footer.
  • ff24af9: Updated the default $font-family-sans-serif and $font-family-monospace to the system font stacks. Tabler no longer bundles a web font, so no font files are downloaded.
  • 9dd26fd: Changed the default theme of tabler-theme.js to auto, following the system color scheme.
  • 7556ae2: Added an auto color mode to theme settings with system prefers-color-scheme support.
  • e3d86c5: Updated apexcharts from 3.54.1 to 7.0.0 and added --chart-{id}-color-{index} variables.
  • 1effe22: Fixed invisible keyboard focus indicators and added prefers-reduced-motion and forced-colors support.
  • 09d419a: Documented the ApexCharts dual license, which applies to the copy shipped in dist/libs, in the readme and chart docs.
  • 080d3aa: Fixed the ApexCharts tooltip arrow staying white on the dark tooltip by setting the --apx-tt-bg and --apx-tt-border tokens.
  • ffe3489: Updated .badges-list to .badge-list and .tags-list to .tag-list, keeping the old names as deprecated aliases.
  • 059bae1: Updated Bootstrap exports to a single source of truth in bootstrap.js and removed the duplicates from tabler.js.
  • 4f6e99b: Moved btn-floating to the end side of the screen, so it does not cover the sidebar.
  • 5018aa9: Fixed .btn-icon to be square by aligning min-width calculation with base .btn formula.
  • a508bb6: Updated hardcoded rem and px values to SCSS variables across core components for easier theming.
  • c71a321: Updated the @tabler/core README with package usage, optional stylesheets, Sass setup and browser support.
  • a0d84f6: Updated the npm package README with badges, quick links, CDN usage, documentation and changelog links.
  • c860288: Fixed icon alignment for .btn-sm and .btn-xl sizes.
  • 1adb710: Fixed .alert-action and .alert-link colors inside .alert-important so links stay readable.
  • 2dc7eda: Updated $border-color-translucent-dark to rgba(128, 150, 172, 0.2) for better dark mode visibility.
  • 09ab0bc: Fixed disabled buttons falling back to currentColor for the border and to a transparent background.
  • febfa9f: Fixed the missing focus ring on buttons and btn-check button groups by using the shared focus ring token.
  • 8324701: Fixed .card-header background being overridden by a background: transparent shorthand.
  • 0187b26: Fixed the card-status-* strips leaving a thin line over the card border and a mismatched corner radius.
  • 70ec683: Fixed card corner radius in tab layouts when tabs sit above or below tab content.
  • c1e1fdf: Fixed the corner radius and the double bottom border on the first and last rows of a table inside a .card.
  • de44d61: Fixed .card-tabs .nav-tabs sharing z-index with .dropdown-menu, which hid dropdowns behind card tabs.
  • 6414238: Fixed barely visible checkbox and radio borders in dark theme by using $input-border-color.
  • b1d49e9: Fixed CountUp to parse formatted number targets and avoid double-start when enableScrollSpy is enabled.
  • 09d419a: Fixed chart colors in dark mode: axis lines and ticks, marker and treemap outlines, and the tooltip title.
  • 070248d: Fixed dark mode link contrast: --tblr-link-color is now a lighter tint of --tblr-primary on dark surfaces.
  • 601e950: Fixed dark mode text selection contrast with a new $selection-bg Sass variable.
  • d0a793c: Fixed the disabled form control background in dark theme with a new --tblr-bg-forms-disabled variable.
  • b1d49e9: Fixed dropdown data-bs-boundary="viewport" to use document.documentElement instead of the first .btn element.
  • c527135: Fixed .input-icon inline-start padding for .form-select.
  • 70f069c: Fixed .form-select keeping its default box-shadow inside .input-group.
  • 9c78cf6: Fixed .bg-gradient conflicts that broke from/via/to rendering and updated the gradient docs.
  • bc24b3a: Fixed --tblr-gray-*-fg tokens to map directly to --tblr-gray-* instead of contrast-based fallbacks.
  • c8b8b24: Fixed gray theme custom properties output using SCSS interpolation and updated default $body-color to $gray-500.
  • 70193fe: Fixed .icon-pulse, .icon-tada and .icon-rotate not animating webfont icons.
  • f0b909d: Fixed the sm and lg size mismatch between form controls, buttons and input groups.
  • 6e656ad: Fixed .input-icon-addon z-index issue with form validation feedback and added default height.
  • b1d49e9: Fixed input mask lazy option to read data-mask-visible via dataset.maskVisible.
  • fc16b6a: Defined --tblr-body-text-align, --tblr-nav-link-font-size and --tblr-nav-link-active-color, which the css already read.
  • a883531: Fixed .list-group-item-{color}: the palette colors had no styling at all and no variant had its background.
  • 9dd26fd: Fixed the markdown table header keeping its surface background instead of going transparent.
  • 9d04e14: Fixed the marketing hero, browser and shape components reading custom properties nothing defined.
  • 9dd26fd: Fixed offcanvas-narrow, which had no effect on the width of the panel.
  • f5f75d4: Fixed print styles: hidden navbar/sidebar, forced light color-scheme, and avoided breaking .card/table rows.
  • 1da70a7: Fixed ScrollSpy throwing on target ids that start with a digit, such as headings like 1. Setup.
  • 464a522: Fixed oversized and mismatched validation icons on .form-select and Tom Select selects.
  • 8bc6fa7: Fixed status color classes to use CSS variables and to include the social colors.
  • c527135: Fixed .steps horizontal overflow on small screens by enabling scrollable overflow below the sm breakpoint.
  • cd0b210: Fixed the typographic .steps rule leaking its guideline and spacing onto the .steps component.
  • 90c42f2: Fixed Tom Select's .ts-dropdown losing its z-index, background, and colors to the bootstrap5 preset CSS.
  • 6849337: Fixed Tom Select styles to use --tblr-* variables instead of undefined --bs-* references.
  • e206d7a: Fixed white space next to the scrollbar by using scrollbar-gutter: stable on html.
  • bf9e7ad: Updated .flag to size by width with $border-radius-xs on xs, and narrowed tooltip padding-x to spacer-2.
  • b8b63d7: Fixed Sass mixed-declaration warnings in the navbar, card, nav and table styles.
  • 9432835: Updated SCSS files to use the border-radius mixin.
  • 9c5d729: Updated stroke-width for .icon-sm from 1 to 1.5 for better visibility.
  • bf9e7ad: Updated .input-group-flat addons to drop the inner border and sit above the control.
  • fa678a7: Updated root color tokens to use CSS light-dark() so paired values live in one :root declaration.
  • 7ae422f: Updated core SCSS to logical properties and a --tblr-dir multiplier, so RTL works with plain tabler.css.
  • 301e778: Updated rgba() calls to the modern color-mix() and color-transparent() functions.
  • 9dd26fd: Fixed the avatar corner radius inside .form-imagecheck-image and set .nav font size to the body font size.
  • 4f6e99b: Fixed the navbar-toggler icon: the middle bar was flex-squeezed, breaking the open-state X and shortening the hamburger.
  • 9c5d729: Added smooth transitions for progress bar width and background-color changes.
  • 1489b13: Added .prose alias for markdown content and updated preview/docs references and redirects.
  • f35aab3: Added the same border and radius to figure images as plain images in .prose and .markdown content.
  • 66dc336: Fixed the caret() mixin by restoring $caret-width to 0.36em.
  • 70f996b: Fixed the sidebar marking two items as selected: a group holding the current page is now emphasized, not filled.
  • 70f996b: Fixed the vertical navbar brand sitting at the far edge on small screens when nothing follows it in the top bar.
  • 9c5d729: Updated skip-link to use visually-hidden for improved accessibility.
  • 346e091: Fixed oversized dist/libs by copying only the runtime files each library declares in libs.json.
  • d2c1271: Fixed tabler-theme.js replacing a server-rendered data-bs-theme when the visitor has no stored choice.
  • 736e604: Updated deprecated global Sass functions to module equivalents (map.merge, string.slice, math.percentage, etc.).
  • b8b63d7: Updated Bootstrap to v5.3.8.
  • 9c5d729: Updated trending component to use arrow-up/arrow-down instead of trending-up/trending-down.
  • 70ec683: Updated $card-status-size default from $border-width-wide to 3px.
  • 666ccd6: Updated shadow tokens (--tblr-shadow-*) to use the new xs2xl and overlay values.

Demo changes

  • 09d419a: Added a charts-advanced page and docs for two y-axes, chart annotations, a zoom brush and synced chart groups.
  • 5e119d4: Added Pay page with dedicated layout, navigation link, and card/PayPal payment form.
  • d8956a0: Updated the preview and docs packages to build with Astro instead of Eleventy.
  • ec94693: Added new card-gradients page showcasing various gradient card styles and components.
  • b0fa655: Added Change Password modal with a strength indicator, confirm validation and show/hide toggles.
  • ad22d04: Added a color palette to the signature pad component for selecting the pen color.
  • b0fa655: Added Confirm Delete modal with a warning icon and a checkbox that enables the delete button.
  • 62178f8: Added new dashboard-crm page with reusable CRM cards.
  • 4ce08ca: Added new Crypto Dashboard page with cryptocurrency portfolio overview, market data, and order history.
  • 118ca4b: Updated the demo pages to show their title and description in the page header, with pageHeader falling back to title.
  • b0fa655: Added Edit Profile modal with avatar upload, personal information fields, social links, and date of birth.
  • 8d8727f: Added language selector dropdown to navbar with flag indicators for multilingual support.
  • 70f996b: Added layout options to the theme settings panel and removed the dead demo script and the layout-combo page.
  • 4ce08ca: Updated the page-menu structure for dashboards and reorganized the navigation menu.
  • 09d419a: Added radar, polar area, treemap, timeline, box plot, bubble and funnel chart types to the charts preview and docs.
  • b0fa655: Added New Task modal with fields for task name, description, assigned user, priority, due date, and category tags.
  • 9c5d729: Added new onboarding page with progress indicator and navigation layout.
  • 118ca4b: Removed the actions preset prop from DefaultLayout; pages now fill a page-header-actions slot instead.
  • cc298a6: Added a PageTitle component and used it with CardSubtitle in place of raw .page-title and .card-subtitle markup.
  • 5363668: Added the missing Prose demo page, linked from the menu but not previously built.
  • 83ec6f8: Added Tour demo page using Driver.js for product tours and onboarding guides.
  • d7cbe87: Added color and vertical props to the Steps component and used it on the steps preview page.
  • 09d419a: Added a straight line chart demo and set stroke-curve to straight on the line and area demos, so the spline ones differ.
  • 99b9ea4: Added a Task List page with tasks grouped by status and a modal for adding new tasks.
  • 1effe22: Fixed keyboard access by turning JavaScript-only <a href="#"> controls into real <button> elements.
  • 1effe22: Added missing ARIA roles and states to Pagination, NavSegmented, Accordion, Steps, tabs, Modal, Offcanvas and CarouselCard.
  • 1effe22: Added aria-label to the Datepicker navigation buttons and made the Dropzone upload area keyboard-operable.
  • 1effe22: Added <fieldset> and <legend> around radio and checkbox groups so screen readers announce the group purpose.
  • 1effe22: Fixed form accessibility with matching for/id labels, aria-invalid, aria-describedby and autocomplete tokens.
  • 1effe22: Fixed the skip link to appear on focus and added missing <main> and labelled <nav> landmarks.
  • 1effe22: Added a label prop to Flag, Payment, StatusDot, Trending and Avatar status badges for screen readers.
  • 0fd35c3: Updated the active-users-2 card chart to use chart-id="active-users-2" with height="11" for a more compact layout.
  • 1adb710: Added action and link examples to the important alerts in the alerts preview and docs pages.
  • 8704725: Fixed root-absolute Button hrefs in demos, so error-page links work in the downloadable package.
  • da11f08: Added a short description and a docs link to each card on the Buttons demo page, grouped into Styles and Colors.
  • 118ca4b: Fixed heading order by making CardTitle and CardHeader render h2, so cards no longer skip a level under the page h1.
  • 632b69f: Updated the changelog page to redirect to tabler.io/changelog instead of rendering CHANGELOG.md.
  • 09d419a: Removed dead chart options (groups, spline, fill, debug, remove-padding, show-labels) from charts.json.
  • 4ce08ca: Added crypto-markets.json and crypto-orders.json data files for the crypto dashboard.
  • 70f996b: Unified the Customize entry: the navbar and the sidebar use the same .navbar-side button with the brush icon.
  • b3e873a: Fixed the value key in selects.json not preselecting options in the demo selects on the modals and form elements pages.
  • 8704725: Updated preview and docs examples to the unified demo component props: variant, color, size, ariaLabel.
  • 0f8dcb0: Updated preview and docs to use the shared date-format, string-format and pseudo-random helpers.
  • 09d419a: Fixed the combination chart demo so it mixes columns and lines instead of drawing plain bars.
  • 3cca8bf: Fixed task cards not showing their due date and removed dead props and classes such as fluidSearch and text-*-lt-fg.
  • 3f1ad9d: Fixed the navbar not highlighting the active page for nested, RTL and settings pages.
  • f2004da: Fixed payment icons on the all-elements page by rendering separate light and dark variants.
  • 9dd26fd: Removed the non-existent mt class from the placeholder card.
  • 1da70a7: Fixed the marketing footer layout, timeline social icons, missing alt and aria-label attributes and invalid nesting in demos.
  • f2004da: Fixed the ribbon example background on the all-elements page to use var(--tblr-bg-surface-secondary).
  • 1adb710: Fixed the sign-in cover page in dark mode by replacing bg-white with bg-surface.
  • c7e895b: Updated the footer to show the version link everywhere and the Generated timestamp only on preview builds.
  • ac87b76: Updated preview templates to replace deprecated font-weight-* classes with Bootstrap-compatible fw-* utility classes.
  • 8704725: Fixed icon-only demo buttons rendering a generic aria-label="Button" instead of a real label.
  • 25f466b: Updated the icon count on the icons pages to read from icons-info.json, so it follows the installed version.
  • 8f86f1f: Restored the import-icons and import-illustrations scripts.
  • 5363668: Made Interface demo pages consistent with the Buttons page: card subtitles, one docs link, responsive grids.
  • 8704725: Updated the marketing CTA Learn more button to the .btn-ghost style.
  • 70f996b: Removed aria-current="page" from menu groups, so only the link to the current page carries it.
  • 7cadbb8: Updated New badges in menu.json to mark only pages added since 1.4, and dropped them from older pages.
  • 8947d7c: Updated the activity feed messages in activity.json and the activity preview page.
  • 863884e: Added DocsLink to the header of 20 more demo pages, including Cards, Charts, Tables and Typography.
  • b90554b: Moved the import:icons and import:illustrations scripts to the repository root, next to import:payments.
  • f35aab3: Added a Tabler Payments docs section with @tabler/payments-* package pages for React, Vue, Preact and Astro.
  • 38df58d: Added the 500 illustration to the preview 500 error page.
  • d2c1271: Removed classes that no stylesheet defines from demo markup and added the onboarding and settings plan pages to the menu.
  • 118ca4b: Added a meta description tag, filled from the page description prop.
  • 7305d84: Fixed Vercel deployment to serve error-404.html as the custom 404 page.
  • 43eee38: Added Progress Step component documentation and cleaned up the progress steps preview markup for cleaner rendered output.
  • 09d419a: Replaced the placeholder chart data on the charts page and in the chart docs with realistic series, and titled every demo card.
  • da11f08: Replaced lorem ipsum with unique placeholder text on the Scroll spy demo page and fixed its menu highlight.
  • 70f996b: Added a Customize entry to the sidebar footer that opens the theme settings panel.
  • 70f996b: The sidebar now always renders the user block, so the vertical layout keeps the profile menu on every page.
  • 552cf1f: Removed the <lastmod> element from sitemap.xml, which carried the build time instead of a content date.
  • da11f08: Reorganized the Star Ratings demo page into Basic/Icons/Sizes and Colors cards with short descriptions.
  • 70f996b: Redesigned the theme settings panel with an illustrated intro, color scheme presets and thumbnail tiles for most settings.
  • bd35fd3: Fixed responsive layouts on the Form Elements page.
  • 53f5244: Updated the Illustration and Empty components to accept only the bundled illustration names, checked at build time.
  • ee4c88f: Updated @tabler/icons to v3.46.0.
  • 369322a: Updated Tabler Illustrations to v1.17.0 with 25 new illustrations.
  • c707018: Added an All Elements page showing every UI component and Bootstrap element.

Docs changes

  • 4a97921: Added Accordion documentation page with usage variants and Bootstrap collapse behavior examples.
  • f5f75d4: Added a Printing docs page covering d-print-* utilities and the media-print mixin.
  • 3277fa0: Added Astro icons library documentation page for the new @tabler/icons-astro package.
  • 73f7c2a: Added Accept: text/markdown content negotiation for docs pages, with q-value parsing and 406 responses.
  • 9dd26fd: Added an Accessibility section to every UI documentation page.
  • d2c1271: Added a Background blur page, gray utilities on the colors page and a language selector section on the navbars page.
  • 135f38d: Added Info, Tip, Warning, Danger and Note callouts, usable in any MDX page, with a reference page under Resources.
  • 9dd26fd: Added installation and usage sections to the chart and countup pages, covering the .chart-* size classes and sparklines.
  • 9dd26fd: Added a class reference table to every component page, from the new classnames front matter.
  • 6e6084a: Added docs pages for the Datepicker and Tom Select form plugins, form-datepicker and form-select-tomselect.
  • 9dd26fd: Updated the docs layout: elevated article panel, rounded sidebar navigation, "On this page" rail and restyled prev/next links.
  • 9dd26fd: Listed every component class in llms.txt, from the classnames front matter.
  • 73f7c2a: Added a llms-full.txt page with all docs in one file, Content-Signal in robots.txt and markdown Link alternate headers.
  • 9dd26fd: Added a source link and a copy-as-markdown button, and moved the class reference to the end of the page.
  • 9dd26fd: Added classnames to the social icons and flags pages.
  • 9dd26fd: Rewrote the autosize, range slider, WYSIWYG and dropzone plugin pages with installation, usage and accessibility sections.
  • 9dd26fd: Expanded the social icons, payments, vector map, inline player, PDF, EPS, illustrations preview and references pages.
  • 9dd26fd: Documented avatar-square, mention, offcanvas-narrow, btn-floating, card-cover, td-truncate and other variants.
  • 416ca63: Added framework integration guides for Laravel, React, Next.js, Vue, Angular, Nuxt, Symfony, Django, Rails, SvelteKit, and Astro.
  • 1adeb68: Added a Docs for LLMs page explaining llms.txt and the .md page mirrors, with a sidebar link.
  • 9dd26fd: Removed the EPS icons page, since @tabler/icons-eps is no longer maintained.
  • fb3d7dc: Added an RTL support docs page covering the dir="rtl" attribute, the published *.rtl.css builds, how rtlcss generates them, and which utility classes are direction-aware.
  • bd4e381: Added Star Rating documentation page with static and interactive rating examples based on existing classes.
  • 8af57f9: Added Tag documentation page with examples for icon, media, badge, checkbox, and list usage.
  • 7b64726: Added a Theme base colors page documenting the five data-bs-theme-base gray palettes in tabler-themes.css.
  • f4c514a: Added an Upgrade to 1.5 page with the breaking changes, renamed classes and Sass updates from 1.4.
  • 2a06640: Added added-in badges to Card gradient, Progress steps and new getting-started guides; fixed background patterns' version.
  • 46da1f7: Updated background patterns documentation with missing pattern variants, transparent utility usage, and size coverage.
  • 1ec82d0: Updated the contributing guide, README and Docker setup for the Astro-based development toolchain.
  • 38df58d: Added the not-found illustration and a search button to the docs 404 page.
  • 9dd26fd: Documented card status, progress, actions, subtitle, surfaces, scrollable body, tables, links and overlays.
  • 9dd26fd: Fixed highlighted code blocks using hardcoded colors instead of the Tabler surface tokens.
  • 9dd26fd: Fixed the copy button in docs examples to be a button with an accessible name.
  • 684f40e: Updated the documentation to explain font sizing and system color CSS variables.
  • 9dd26fd: Fixed docs example code blocks gluing inline elements like <label> and <input> onto one line.
  • 9dd26fd: Added a dark background option to the docs <Example> component.
  • 9dd26fd: Updated docs examples to use the <Icon /> component and consistent Prettier formatting.
  • d2c1271: Fixed duplicate ids and missing targets in the modal, offcanvas, page headers and page layouts examples.
  • 9dd26fd: Fixed keyboard access in docs examples for carousel controls, disabled links, btn-loading and progress bars.
  • 9dd26fd: Fixed install snippets pointing at urls that do not resolve on the vector map, inline player and icon pages.
  • 9dd26fd: Fixed docs examples using classes that do not exist, such as alert-facebook, btn-close-white, bg-gray, btn-xs, alert-title and hr-text-center.
  • 9dd26fd: Added tooltips and accessible names to the footer icon links.
  • 9dd26fd: Stacked form docs examples in a column and tightened the sidebar nested menu indent.
  • 9dd26fd: Moved the form pages backed by a third-party library into the Plugins section.
  • 9dd26fd: Renamed form docs pages without the form- prefix and flattened the Illustrations and Emails sections.
  • a0d84f6: Updated the How to Contribute guide with starter issues, Codespaces setup, commands and PR conventions.
  • f35aab3: Added Introduction pages with quick starts and cover images to the Icons, Illustrations, Emails and Payments docs sections.
  • 1ec82d0: Fixed documentation formatting issues: heading hierarchy, missing image alt texts and broken list structure across docs pages.
  • 9dd26fd: Split the getting started menu group into Getting started and Resources.
  • 9dd26fd: Updated the docs navbar to use container-lg.
  • 4afc2ea: Updated the prose on 75 docs pages to plain English, shortened the page summaries and merged duplicate sections on the button and card pages.
  • 9dd26fd: Moved components that need a third-party library into the Plugins section.
  • 9dd26fd: Moved the Website, Preview and Support links from the docs sidebar to the top navbar.
  • 9dd26fd: Restyled the related and prev/next cards, and moved prev/next below the article panel.
  • 9dd26fd: Restored the docs.scss source for the docs styles, compiled with Sass instead of a checked-in docs.css.
  • 9dd26fd: Fixed the search modal using the Algolia palette instead of Tabler colors, including an unreadable selected result.
  • 9dd26fd: Darkened the docs sidebar links, kept rows to one line and added a visible focus ring.
  • c430cfe: Updated UI component docs to singular file names and frontmatter, with redirects from plural routes.
  • 826a073: Added sitemap.xml and robots.txt endpoints for the docs site and fixed docs layout title rendering outside production.
  • 2f8f495: Fixed the docs header not staying sticky, and the table of contents sliding under it.
  • 9dd26fd: Documented sortable headers, selectable rows, stacked mobile tables, and outline, dot and icon-only badges.
  • 9dd26fd: Rewrote the timeline and datagrid pages with markup, variants and accessibility notes.
  • 9dd26fd: Fixed plugin demos showing their stock styles, because library CSS loaded after tabler-vendors.css.
  • ee4c88f: Updated @docsearch/js and @docsearch/css to v5 and kept the docs search on the keyword-only entry point.
  • 1adeb68: Fixed html comments in docs code examples running into the closing tag of the element before them.
  • 09d419a: Fixed the small chart example on the chart docs page, which shared an element id with the line chart example and stayed empty.
  • c547329: Fixed the Plugins card on the docs homepage linking to a 404 /plugins route instead of /ui/plugins.
  • 1da70a7: Fixed the docs logo link name, dead example links in the navbar and offcanvas pages, and the vector map error on its plugin page.
  • ee4c88f: Fixed docs pages with callouts falling back to their MDX source in llms.txt instead of the rendered examples.
  • c71a321: Fixed the Sass import in the framework guides to use @use instead of the deprecated @import.
  • 70f996b: Documented the layout theme settings on the color modes and page layouts pages.
  • 1adeb68: Fixed the .md docs mirrors showing component tags like <Icon /> instead of the rendered html.
  • 08cad98: Updated the Progress Bar documentation with new variants and full-width stacked previews.
  • 4d04c10: Removed the unused bootstrapLink front matter field from DocsLayout, DocsMdxLayout, and all docs pages.
1 days ago
fullcalendar

v7.1.0

Event Rendering

  • PERF: DayGrid with large number of events, when dayMaxEvents:true (limited by natural row height), only a necessary subset of events will be DOM-inserted, not all
    • BREAKING: As a result, a subset of events will be run through event render hooks like eventContent
  • DayGrid event slicing
    • FEATURE: Better event packing, including smarter positioning decisions for fewer slices
    • FEATURE: Related slices have more similar y-coordinates
    • FIX: Events unnecessarily going to +more link when higher-level slot would accommodate
  • TimeGrid event positioning
    • FIX: Events with custom ordering and eventOrderStrict:true can overlap with slotEventOverlap: false (#7914)
    • FIX: Overlapping events can have tiny width when event content not visible (#7879)
  • FIX: In Timeline and TimeGrid, when custom eventOrder, DOM order respects start-time first, then eventOrder. Better for a11y and tabbing through events sequentially
  • FIX: React StrictMode: DayGrid events permanently invisible (#8088)

Resource Views

  • FIX: Timeline view, smoother virtualized fast scrolling, especially with trackpad and especially while scrolling up with irregular height resource rows
  • FIX: Timeline view, account for DST in timeslots (#7620)
  • FIX: Resource TimeGrid/DayGrid, filterResourcesWithEvents should exclude columns (#6149)

Print Rendering

  • FEATURE: Print renders business-hours for print-previews that enable it
  • FIX: Better page-wrapping of tall DayGrid rows with many events
  • FIX: Better page-wrapping of tall Resource-Timeline rows with many events
  • FIX: Events are not visible when we print using window.print() (#8097)

Misc

  • PERF: General improvements with option-changes. Users of @fullcalendar/preact must upgrade their preact peer dependency to >=10.29.8
  • FIX: Screen-reader improvement for Resource-Timeline timeline header text