Jixoai UI v0.4.0: better visual effects, broader content rendering
Press effects attach to any element you render; code highlighting picks its engine on demand and detects the language; icons gain a component; typography becomes a system, and math and diagrams get components. The registry grows from 113 items to 136. Three migration actions, none with a compat layer.
$ git log v0.3.0..v0.4.0 --oneline | wc -l
175
Jixoai UI 0.4.0 is out (2026-09-10). 175 commits; the registry grows from 113 items to 136, a net gain of 23 (25 added, icons and alert-dialog removed). The headline is effects becoming attachments; highlighting, icons, typography, math and diagrams, surface structure, and the docs canvas land on the same line. Three migration actions, none with a compat layer.
Effects attach to any element you render
The press effects are Svelte attachments now. shimmer, pulse, rainbow and ripple mount through {@attach pressEffect(fx)} on any element you render; the effect props retire from press-button, icon-button and chip, and the components themselves no longer carry effect knowledge.
<script lang="ts">
import PressButton, { shimmer, rainbow } from '$lib/ui/press-button/press-button.svelte';
import { pressEffect } from '$lib/ui/press-button';
</script>
<PressButton variant="fill" {@attach pressEffect(shimmer())}>Deploy</PressButton>
<PressButton variant="outline" {@attach pressEffect(rainbow())}>Cancel</PressButton>
Svelte's attachments are first-class syntax, no experimental flag. They are a different contract from actions, so the effect exports were rewritten as attachment factories and every use: mount in the repo moved in the same change (32238a08).
Mechanism: the attachment contract and the migration caliber
The value of {@attach expr} is the attachment itself, with the signature (element) => cleanup; a replaced param re-mounts by identity, with no update channel and no destroy object. Action-shaped functions crash when attached directly, and the repo's {destroy}-returning internal actions silently leak under a naive syntax swap, so svelte/attachments' fromAction(action, () => param) bridges the action-shaped internals. 39 use: mounts moved.
shimmer and rainbow paint the host's own border ring, with ringW driving the ring width; the fill rides a number | null | Canvas channel where solidFill() mints any CSS color into a guaranteed-opaque 0xRRGGBB. ripple moves to a viewBox-free svg seat, carrying the host's inherited silhouette in user units, clipped per layer and softened by feGaussianBlur.
The effects family now has its own home at /docs/effects.html, with glass's two-layer playground moved in whole and the old route /docs/components/glass.html retired.


Highlight engines install on demand, languages detected automatically
The highlight layer splits into a zero-dependency core plus six installable backends (microlighter, highlight.js, prismjs, shiki, sugar-high, tree-sitter). One engine ships by default with all of its capability; the rest are opt-in. Previously the code-card and highlight items wrote prismjs and microlighter into their npm dependencies, so installing one component installed three engines; this release fixes that.
Language detection is new. A LanguageDetector abstraction plus the built-in DLD (default language detector) loads in four layers on demand, lang="auto" enables detection, and the markdown faces now default to auto. Inline code defaults to microlighter: 4.5KB, zero markup, plain-text copyable.
Mechanism: the four DLD layers
Layer one, filename/ext: an extension map plus an exact-basename map (Dockerfile, Makefile and the like). Layer two, shebang and modeline, first line only. Layer three, special structures: JSON by full parse, <?xml plus <svg> for svg, YAML, TOML and INI by regex features; this layer does no programming-language fingerprinting. Layer four, statistical detection, powered by the betlang wasm build.

Icons gain a component and lazy loading
The icon system gained its component face. <Icon name="check" /> comes with a type-safe IconName union and size and strokeWidth props, replacing 197 {@html icons.x} injection sites (proposal caliber: 77 in the registry plus 120 on the docs site).
The plugin face makes the channel the public API. @jixoai/ui-vite-plugin/icons/[lucide|md|ph|rx] are independent entries of the same package, with lucide on by default and the rest enabled by import; jixoai({ icons: { library } }) generates icon-set.gen.ts, folding custom icon names into IconName. Icons load in lazy chunks, so a consumer never pays for the whole set.
This release also adds a real-project E2E tier for icons. examples/hmos-icons is a real consumer, installed and built for real, and it exposed two defects on the spot (00d3fd92).
Details: the two defects the E2E tier exposed
svgo minifies every icon's ids to the same short token (a), so two Sketch exports inlined on one page cross-resolve their use targets; scopeIconIds now prefixes every packed id with its canonical name. The RAW safety gate rejected the Sketch-export shape of use plus xlink outright, which made channels near-useless for exactly the third-party sets they exist for; the fragment-law carve-out now passes same-document references while external references still reject. The plugin test suite stands at 398/398.
![]()
Typography becomes a system, and markdown constructs become parts
Typography gains a prose item (the <Prose> provider) and a typography lib. All eleven knobs (size, leading, family, ink, gradient, ground, align, indent, initialLetter, wrap, hyphens) use absentSlot semantics: not setting one means it stays silent, the nearest upstream setter wins, and an outer <Prose size leading> can never beat an inner <Markdown typography=>. That ladder is the sovereignty contract, with no JS masking.
The reading-content family extracts every markdown construct that had no registry part, so editing the component is how you change the markdown rendering. blockquote covers the GitHub border, the tinted callout, and the manuscript indent; heading renders native h1-h6 by level; Link marks external links with a suffix icon by default; List splits into the strict ol and ul cases. text.svelte implements <Text mark="…"> and exports P Strong Em Del Mark Ins Sub Sup from the same module. The markdown face itself is streaming, mapping its AST onto registry parts, with code blocks riding code-card's generation-guard async repaint.
Details: blockquote's literal slots
The left rule splits into two literal slots: rule picks an inset shadow or a border, and ruleSize takes 1px, 4px or 8px. heading carries the em ladder migrated out of the markdown face sheet.

Math and diagrams get components
lib katex is a thin facade over renderToString. One import gets you the math markup and the fonts: the katex.min.css import is the module's only side effect, the fonts ride the npm package, and no consumer configures fonts. The naming follows the syntax standard: LaTeX's components are math-block and math-inline, and mermaid keeps its own name.
lib mermaid-engine is a lazy singleton, code-split because the engine is about 1MB. The acceptance round landed two rulings: mermaid's CROSSED pin brings its own canvas, painting the viewport from the target theme when a theme is pinned explicitly, and math-block gains a FIT variant that scales instead of scrolling, which is the print default (8893aa35).
Details: the katex facade's defaults
Three jixoai defaults, each overridable by the caller's same-key option: output: 'htmlAndMathml' (visual HTML plus the screen-reader MathML), throwOnError: false (invalid TeX paints in place instead of throwing), and an errorColor bound to the var(--error) token. Options pass through untouched, and registerMacros is the site-scoped extension point, last key wins.
Overlays and cards share one structure
The structural kernel of 2026-09-09 collapses "the bottom action area" into one law. head, body and foot bands plus sticker-style structure, with every rule anchored on the data-jx-* attribute family so any element that carries the stamp is structure; the body alone owns scrolling; the inline ruler is served by CSS. Dialog sheds its own flesh for Card's dialect, and ButtonBar retires.
The action-band law then generalized to sheet, system-dialog and the canvas dock, with buttons filling their band vertically instead of floating in its padding. AlertDialog is renamed SystemDialog, and the registry item alert-dialog becomes system-dialog (ed1d8a3e, the range's only commit carrying a ! marker).

Docs demos carry source by default
ComponentCanvas's permanent playground lane became a floating, collapsible, draggable dock inside the canvas, mounted expanded and collapsing to an icon chip, with its controls assembled through ItemGroup; the canvas itself slims down to a code-card-shaped card. The lane used to sit there permanently, eating 18-22rem of stage width.
The companion canvas-everywhere sweep routes every docs demo through the canvas. Previously, across 114 pages, 443 ComponentCanvas instances stood against 1580 SectionCard instances, leaving readers unable to see the code behind most of what they saw; demos now carry a source lane by default (e96f55bb).

One command installs a whole group
The CLI gains group aliases. npx jixoai-ui add effects expands the whole group, and npx jixoai-ui add effects/glass takes one member.
@jixoai/ui-vite-plugin 0.3.0 ships alongside: typography brings the prose scope and the parts' R2/R3, icons bring the real-project E2E tier above, and canvas brings the dock's acceptance rulings (the acrylic popover material, the grip-vertical icon, and position: absolute retired).
Migration (breaking changes)
Three migration actions, none with a compat layer: how effects mount, two item renames, and the glass class. Only the rename commit carries a ! marker in the repo; the other two state their breakage in their own proposals.
The first is effects, from a component prop to an attachment:
<PressButton {@attach pressEffect(shimmer())}>Deploy</PressButton>
The second is two renames: alert-dialog becomes system-dialog, and icons splits into icon and icon-set. The lowest-friction path is to re-add items under the new names:
npx jixoai-ui add system-dialog icon icon-set
The third is glass. The hand-tuned .jx-glass class retires and blur()'s defaults take over (14px blur, 1.35 saturation, 68% fill), so an unconfigured migration is computed-equivalent. The full mounting map:

Other changes
Glass: the band gained three parallax scenery layers on seamless infinite loops (a8c6d02f);
blur()subsumes every hand-tuned paint from before, andliquid()andliquid.apple()mount the kube-faithful lens.Highlighting: the
langDetectorcontract enters the core item, AUTO_LANG lands in code-card, and the detection lab enters the docs.Typography: the oklch token palette goes to v2, with the secondary leading ladder, the fused variant and the shared text-modifier kernel (341b101a).
The remaining 49 commits are fixes.
175 commits from 0.3.0 to 0.4.0, strict conventional-prefix count: feat 41, fix 49, docs 66, chore 15, test 1, refactor 1; plus 2 non-standard prefixes (polish and harden).
Acknowledgements
Svelte's attachments syntax and the fromAction bridge, kube.io's source-level liquid glass recipe, and GitHub's linguist language data set the upstream bounds of this release's three capabilities.
Links
Changelog: GitHub Release v0.4.0 · compare v0.3.0...v0.4.0
Docs & registry: ui.jixoai.com · the effects family home · README
Where the contracts come from: the effect-attachments proposal · the card-surface-kernel proposal (the proposal directory entered the repo at 0.5.0)
Upgrade: see the Migration section above (change the mounting form, re-add items under the new names, and let
blur()'s defaults carry glass)Feedback: GitHub Issues
On this blog: Jixoai UI v0.3.0 (published 2026-09-06)
Chinese version: /zh/blog/2026-09-10-ui-0-4-0/
