Skip to content

Jixoai UI v0.4.0: better visual effects, broader content rendering

jixoai v0.4.0 ui

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.

The rainbow canvas: three neutral host buttons (upgrade, subscribe, ⌘) share one fx object, the host's own border is the flowing ring with ringW driving its width, and the fill rides the auto / null / solidFill channel

The effects home: the family law, an effect being an attachment factory you mount rather than a component you render; five chips list the two mount channels, the action bridge and the mount-point attributes

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.

The complete code-card: the spawn.ts filename tab, the shiki · ts engine badge top right, and the powered by Shiki · theme: jixoai footer with its copy control

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.

The icons page, component face: the size ladder, one prop rendering across 12 / 16 / 24 / 32

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.

The prose page knob showcase: size and leading scaling by inheritance on the left (ambient 14px against 17px), align justify with hyphens and wrap pretty on the right

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).

The card page: the structural kernel and its head / body / foot bands, with the inline action seat Cancel / Save in the foot

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).

component-canvas: a canvas rendering a canvas, the outer LIVE stage embedding a simplified second instance, each with its own Code lane

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:

The migration table: what replaces the action directive, the builder prop and the attachments record

Other changes

  • Glass: the band gained three parallax scenery layers on seamless infinite loops (a8c6d02f); blur() subsumes every hand-tuned paint from before, and liquid() and liquid.apple() mount the kube-faithful lens.

  • Highlighting: the langDetector contract 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