Skip to content

Releases: once-ui-system/core

v2.0.0

Choose a tag to compare

@lorant-one lorant-one released this 25 Sep 09:12

[2.0.0] — 2026-09-25

Once UI 2.0 is on latest:

npm i @once-ui-system/core

2.0 is the release where Once UI becomes a system an AI can build with
correctly. One spec drives the docs, the ai/ harness, the validator and the
codemod, so what an agent reads is what the package accepts. Utilities and
responsive classes are generated, and breakpoints are fixed, so CSS and
JavaScript agree on every width and a responsive Flex can be a server
component.

Upgrading from 1.8.x. MIGRATING.md is the checklist, in the
order that keeps the app building: run the codemod, install the peers for the
subpaths you use, fix what the codemod reports, import LayoutProvider from
@once-ui-system/core/next in a Next app, check the browser floor (Chrome 111,
Safari 15.4, Firefox 113), drop custom breakpoints, and re-shape any
ScrollContainer. Most apps finish in the first two steps.

What is in it. 2.0.0 is 2.0.0-alpha.3 plus the entry below. The four
alpha entries that follow are the full record of what changed from 1.8.x and
why; read them together, oldest first. Two things are by design rather than
open questions:

  • A bundler is required. Components import their own .module.scss files,
    so Once UI runs in apps built by a bundler that compiles Sass modules
    (Next.js, Vite and the like), as it always has. Plain Node require/import
    of the package is not supported.
  • @once-ui-system/foundations stays private. Core inlines its SCSS and CSS
    at build time, so there is nothing extra to install.

Added

  • sizes takes the Media shorthand everywhere it reaches Media.
    <Media sizes={1200}> stands for (max-width: 1200px) 100vw, 1200px, but
    Carousel (its slides and thumbnail.sizes), Swiper, OgCard, Book and
    MediaUpload typed their sizes as string while passing it straight to
    Media — so the number worked at runtime and failed to type-check. They take
    string | number now, resolved by Media as before. The ai/ harness lists
    the wider type, so an agent validating against the alpha.3 harness would
    reject the shorthand on these five.

v2.0.0-alpha.0

v2.0.0-alpha.0 Pre-release
Pre-release

Choose a tag to compare

@lorant-one lorant-one released this 16 Sep 18:35

The first preview of Once UI 2.0.

This is a pre-release. It goes to the alpha dist-tag, not latest. npm install @once-ui-system/core keeps resolving 1.8.4, and every ^1.8.x range in the wild is unaffected — a prerelease doesn't satisfy a stable range, not even ^2.0.0 or *. You have to ask for it by name:

npm i @once-ui-system/core@alpha

It's a preview of 2.0, not 2.0. The API here is what we intend to ship, but the point of an alpha is to find out where that's wrong — please report what breaks.

Migrating from 1.8.x

Five things, in the order that makes sense. MIGRATING.md walks each one.

  1. Run the codemod. It applies every prop rename below, component-scoped, and is a no-op on its own output — so it's safe to re-run.
    curl -O https://raw.githubusercontent.com/once-ui-system/core/main/scripts/codemod-2.0.mjs
    node codemod-2.0.mjs src --dry   # read the report first
    node codemod-2.0.mjs src
  2. Move chart, CodeBlock and MediaUpload imports to the /data, /code and /media subpaths, and install the peer each one names. The codemod does this too.
  3. Rewrite ColorInput's onChange body by hand — the signature changed, and a rename can't express that.
  4. Next apps: import LayoutProvider from @once-ui-system/core/next to keep 1.8.x link and image behaviour. One line.
  5. Check the browser floor. Scheme tokens are oklch() now.

Nothing else is required to build.

Breaking

  • Prop API standardisation — a batch of renames, all codemoddable.
  • Timing props are milliseconds, consistently.
  • fill means layout everywhere, and Skeleton drops its second size scale.
  • Charts, CodeBlock and MediaUpload move to their own subpaths (/data, /code, /media).
  • IconName is a union, not string. A name that used to render a blank space is now a type error — that surfaces bugs an app already had rather than creating work.
  • Textarea grows with its content by default.
  • LayoutProvider no longer takes breakpoints — the five steps are fixed.
  • Colour props that paint into SVG accept tokens again.
  • Scheme tokens are oklch(), which sets a browser floor: Chrome 111, Safari 15.4, Firefox 113.

Next.js is now optional

Core no longer imports next/* at runtime. ElementType (behind SmartLink, and any Button / Card / ToggleButton with href), Media, Logo, MegaMenu and Kbar render through an adapter layer whose defaults are plain DOM — <a>, <img>, window.location.assign.

Next apps keep 1.8.x behaviour with a one-line import change:

- import { LayoutProvider } from "@once-ui-system/core";
+ import { LayoutProvider } from "@once-ui-system/core/next";

Verified by installing the packed tarball into a React app with no next in node_modules and server-rendering SmartLink, Button, Media, Row, Column, Text and Schema.

Also in this release

Added — ThemeSwitcher gains collapsed; focusRing on Input and Textarea; StylePanel is composable with host-owned state; ThemeProvider takes persistence; body text scales without touching headings; generateColorScheme and schemeAlphaVariants.

Fixed — the mega menu's dropdown shadow is no longer clipped; background="transparent" and solid="transparent" actually do something; ThemeSwitcher exposes which theme is active; a failed emoji fetch no longer fails the build or the publish; StylePanel no longer writes settings straight to localStorage; Carousel and Swiper stop blurring the edges of their own images; a larger icon registry.

Changed — spacing utilities are generated rather than hand-written; .align-between, .align-around and .align-even are gone; agent guidance (ai/rules.md, ai/recipes.md) gains rules on proportion and reveal-on-hover.

Full detail in the changelog.

Known and deliberate

  • The published package is bundler-only. dist is ESM syntax in .js files without "type": "module", so Next.js, Vite and friends load it and plain Node require/import does not. This predates 2.0; fixing the module format is a decision that hasn't been made yet.
  • @once-ui-system/foundations is not published. Core inlines its SCSS and CSS at build time, so you install nothing new.
  • Breakpoints are fixed. See MIGRATING.md §7.

Docs

The documentation site now describes 2.0: https://docs.once-ui.com

Still on 1.8.x? The 1.x docs are frozen at https://v1.docs.once-ui.com

v.1.8.5

Choose a tag to compare

@lorant-one lorant-one released this 16 Sep 19:32

A patch on the stable line. npm install @once-ui-system/core gets it; nothing to migrate.

Fixed

  • CountFx no longer freezes when value reverts mid-animation. A run started from the last completed value, so changing value back while one was still in flight hit an early return after the frame had already been cancelled — the number stayed stuck on an arbitrary intermediate value. A monthly/annual pricing toggle clicked twice in quick succession was enough. Runs now start from whatever is on screen, so an interrupted transition reverses from where it got to and always settles exactly on the target. Reported and diagnosed by @chanderlud in #134.

  • The published package now carries its license text. package.json has declared "license": "MIT" all along, but the grant itself never shipped — it lived only at the monorepo root, and npm includes a license only from the package directory. No tarball up to and including 1.8.4 had one.

Full changelog: CHANGELOG.md

v2.0.0-alpha.3

v2.0.0-alpha.3 Pre-release
Pre-release

Choose a tag to compare

@lorant-one lorant-one released this 25 Sep 09:11
582c771

[2.0.0-alpha.3] — 2026-09-24

The fourth alpha preview, on the same alpha dist-tag. npm install @once-ui-system/core still resolves the 1.8 line; asking for this one stays
deliberate:

npm i @once-ui-system/core@alpha

Classified as a pre-release of the 2.0 major. No prop, export or token is added,
removed or renamed; everything here is a fix, plus an internal import refactor
that leaves the public entry points as they were. Two fixes change rendered
output for code that already works around the old behaviour, which is worth
checking on upgrade: a titled BarChart no longer grows past its height (its
card is height tall, like the other charts, with the plot fitting under the
header), and a caller that padded a chart's height to make room for the
header will now get a taller plot rather than a clipped one.

The ai/ harness is regenerated for the version; no component's props changed.

Changed

  • Core's own files import from the file that defines a symbol, never from a
    barrel.
    195 imports across 160 files went through a directory index.ts
    (".", "../components", "../../contexts"…), so importing any one
    component pulled in every module that barrel re-exports. In a Next.js app
    that put all 130 client components on every page, the 150 KB emoji dataset
    among them. The public barrels are unchanged, and
    import { Button } from "@once-ui-system/core" works exactly as before. What
    this unlocks is per-component imports
    (@once-ui-system/core/components/Button), which now ship only what they
    name: on the Once UI site they cut the JavaScript every page loads from
    542 KB to 472 KB gzipped. A test fails if an internal barrel import comes
    back.

Fixed

  • A chart's plot fits inside its card again. LineChart, BarChart,
    LineBarChart and PieChart used height twice: as the card's height and
    as a min-height on the plot row under the header (added in 1.8.0). A chart
    with a title therefore asked for header + height inside a card that is
    height tall, and the plot hung past the card by the header's height —
    measured at 45px with a title and 65px with a description — clipped by its
    own overflow: hidden, with the axis labels and legend spilling onto
    whatever came next. BarChart grew instead, because its card used
    minHeight. height now sizes the card once, on all four, and the plot
    takes the height the header leaves.

  • Numeric padding and margin no longer vanish on the client. Flex,
    Grid, Text and Heading wrote a number as the CSS shorthand next to the
    four longhands, leaving the unset longhands undefined. The server skips
    those, so server-rendered pages were fine. On the client React writes an
    undefined longhand as '' after the shorthand, which clears it:
    <Column padding={1}> mounted in the browser had style="", and a box that
    went from four numeric sides to one padding lost all of it on the update.
    Found in Aveiro's editor, where a block's spacing collapsed as soon as all
    four sides matched. Numbers are now written as the four sides, resolved
    side → axis → all, the same precedence as before. Tokens are unchanged:
    they are classes, not inline styles.

  • A clickable Card runs its handler once, and its label names the link.
    onClick was set on both the card's button and the surface inside it, so
    a click on the content bubbled and ran the handler twice; a card that
    toggled its own selected state flipped and flipped back. The handler now
    sits on the focusable element only (through onLinkClick for an href
    card). aria-label, aria-labelledby and aria-describedby also went to
    the inner surface, leaving the link or button named by whatever text the
    card contained; they now land on the element that takes focus.

  • FadingLettersFx no longer stalls while the page is busy. It animated
    filter: blur(), which browsers cannot hand to the compositor (a changing
    blur radius moves pixels), so the letters ran on the main thread and froze
    mid-blur whenever script ran during their entrance: hydration, a chunk
    loading, a WebGL context starting. The blur is now fixed, on two copies of
    each letter drawn with ::before and ::after from a data-letter
    attribute, and only opacity and transform animate, so the whole effect
    runs on the compositor. The look is the old curve, sampled: a heavy blur,
    a light one, then the sharp letter, on the same timings and delays. Each
    letter now wraps its character in an inner span; the copies are generated
    content, so the text is still in the DOM once.

  • A collapsed ThemeSwitcher no longer loads open and then snaps shut.
    The server cannot know the visitor's theme, so the group used to render
    with every option showing and collapse on hydration. It now renders at its
    one-option size, invisible, and appears once the active option is in place,
    with the swap unanimated. Touch devices, which always show every option,
    are unchanged.

  • ThemeSwitcher direction="column" reserves the space it draws. The
    anchor was a hard-coded 40px square, but the options are 32px IconButtons
    in a 1px border: 34px. The extra 6px pushed the control off-centre in its
    row. The anchor, and the caps the options open and close to, now follow
    the button size.

  • MegaMenu works from the keyboard. A group with a dropdown and no
    href rendered its trigger as a div, so it was not in the tab order at
    all, and every panel opened on hover only. Those triggers are now buttons
    with aria-expanded and aria-controls that toggle on Enter or Space; a
    trigger that is also a link opens its panel on keyboard focus. Tab moves
    from an open trigger into its panel and on to the next trigger, Shift+Tab
    comes back, ArrowDown opens a panel and the arrows move within it, and
    Escape closes it and returns focus to the trigger. Pointer behaviour is
    unchanged.

v2.0.0-alpha.2

v2.0.0-alpha.2 Pre-release
Pre-release

Choose a tag to compare

@lorant-one lorant-one released this 25 Sep 09:10
582c771

[2.0.0-alpha.2] — 2026-09-23

The third alpha preview, on the same alpha dist-tag. npm install @once-ui-system/core still resolves the 1.8 line; asking for this one stays
deliberate:

npm i @once-ui-system/core@alpha

Classified as a pre-release of the 2.0 major. The ScrollContainer tile
defaults are removed rather than deprecated, which is a major-criteria change
("removing or renaming components, props, tokens, or CSS classes", and
"changing default behavior in ways that alter rendered output for existing
code") — it rides the 2.0 line rather than a minor, and existing carousels need
their shape passed in explicitly. Everything else here is additive or a fix.

Per the AI-consumer rule, ai/manifest.json, ai/catalog.json and
ai/spec.json are regenerated in this release: ScrollContainer gained six
props since alpha.1, so an agent validating against the previous harness would
have rejected all of them.

Added

  • ScrollContainer moves a track instead of scrolling a box. A scroll box
    cannot do any of the three things asked of a carousel: its contents are
    clipped at its own edge by definition, so no tile can peek past it; its
    scroll range has two hard ends, so there is nowhere for a wrap-around to go;
    and the browser owns the easing, so a drag can only set scrollLeft and
    hope. The track is now laid out once and moved with a transform, which
    removes all three limits at once and makes the motion a CSS transition the
    component controls.

    New with it: infinite continues past the last item into the first in the
    same direction, with no rewind and both controls always enabled; markers
    (true, "top" or "bottom") shows how many items there are and which is
    in front, and jumps to one when pressed; draggable (on by default) has the
    track follow a mouse, pen or finger, claiming a gesture for one axis on its
    first movement so a vertical swipe still scrolls the page, and swallowing the
    click at the end of a drag so it does not also open the tile; step sets how
    many items a control press moves; and clip keeps everything inside the
    component's own box for callers who want the old edges back.

    This changes existing carousels. Tiles used to arrive with
    aspectRatio="3/4", border, radius="xl" and a width range already set,
    so anything that was not a tall bordered portrait card had to override four
    properties before it could begin. A tile now contributes only what a track
    item cannot do without — refusing to shrink, and a minWidth floor so a
    carousel of fill-width content has something to fill. Shape and size come
    from the caller, through the same rest props that always landed on the tile.
    Because the browser is no longer doing the scrolling, a trackpad's horizontal
    gesture no longer moves the track, which is why drag is on by default and the
    controls are never hidden.

  • ThemeSwitcher collapsed can open downwards, with direction="column".
    The collapsed group grew sideways, in the flow of the layout, which works
    where it has room beside it and not where it has neighbours: the group
    widening pushes them along, and the option the pointer is resting on slides
    out from under it. In a header that is every time it opens.

    direction="column" splits the control into an anchor and a panel. The
    anchor is what the layout sees and stays the size of one option however many
    are showing; the panel is taken out of the flow and hung from the anchor, so
    it grows over the page instead of through the header. Measured in a 56px
    sticky header with neighbours on both sides: nothing beside it moves and the
    header does not change height.

    It opens downwards rather than centring on the active option. An open panel
    is 124px against a 56px header, so anchoring it on the second or third option
    would put its top edge 34px and 76px above the viewport.

    The active option leads. In fixed order the panel's first slot belongs to
    whichever theme is listed first, so the icon already under the pointer
    changes identity as the panel unrolls — hover the moon and the computer
    arrives under your cursor, and a click without looking sets the wrong theme.
    Leading with the active option keeps the invariant that the theme in force is
    the one in the anchor slot, before opening and after choosing. The reorder is
    done in the markup rather than with CSS order, so tab order and visual
    order stay the same thing, and stable keys mean a focused button keeps focus
    across the move.

    direction defaults to "row" and is ignored when the group is not
    collapsed, so nothing that already uses ThemeSwitcher changes. Devices that
    cannot hover still get the group always open, and there it takes its space in
    the layout rather than hanging over what is underneath.

  • ScrollContainer takes active and onActiveChange. active brings an
    item to the front by its index, by the shorter way round when infinite;
    onActiveChange reports the item that arrived there, however it got there.
    Together they let something outside the component stand in for the markers —
    a row of names, a set of thumbnails, a table of contents — which previously
    was not possible at all: the index was internal state, and the forwarded ref
    was never attached to anything, so there was no handle on the run from
    outside.

    active is a command rather than a lock, and the effect that reads it is
    keyed on the prop alone. Were it to depend on where the run actually is,
    every drag would be undone a frame after it ended by an effect insisting on
    the last value the caller passed, and the run would fight the pointer. An
    active that never changes therefore does not pin the run in place;
    onActiveChange is how a caller stays level with it.

  • ScrollContainer takes proximity. Tiles scale by their distance from
    the one in front, so the run has a focus that moves with it. It is continuous
    rather than stepped — the scale is computed from the live drag offset divided
    by the tile pitch, so halfway through a drag the outgoing and incoming tiles
    are the same size and the emphasis crosses between them as the pointer moves,
    instead of snapping when the track settles. true is a gentle default; a
    number is how much smaller each whole step away is, as a fraction. Tiles stop
    shrinking at 72% so a long run does not trail off into nothing. Off — the
    default — a tile carries no transform at all, not scale(1), so it never
    becomes a containing block for anything the caller positioned inside it.

  • Form, a layout for a set of fields. density carries the gap and the
    grouping as one decision, because they are one decision: "stacked" fuses
    the fields into a single control, with the borders of neighbours collapsed
    onto one hairline and only the outside of the group left round, while
    "tight" and "spacious" are ordinary gaps that leave every field its own
    border and radius. columns lays the fields out in a grid and takes a
    per-breakpoint map; a field can occupy more than one column with span;
    size sets the size of every field in the group, and a field that sets its
    own still wins.

    The corners are the point. Passing corners="top" | "none" | "bottom" down
    a stack is positional bookkeeping that breaks the moment a field is
    conditionally rendered or reordered — hide the third of four and the group
    ends on a square edge. Form derives them from the children that actually
    rendered and from where each one lands in the grid, so a ragged last row, a
    spanning field and a column count that changes with the viewport all come
    out right. A focused field is lifted above its neighbours, and nothing in
    the component clips, so the focus ring stays whole.

    Input and Textarea gained a span prop, read by Form and stripped
    before it reaches the DOM, and mark their bordered box with
    data-surface="field" so Form can reach the element that actually carries
    the radius. Children that do not mark a surface are laid out and spaced but
    keep their own corners.

  • Docs pages say whether you can install what they describe. A status
    frontmatter field — "alpha" or "unreleased", absent meaning available on
    latest — puts a tag in the sidebar and a notice under the page title.
    VersionBanner already said the site documents 2.0, but it says one thing to
    every page, and it cannot tell "shipped in the alpha" from "not published
    anywhere yet": it told both to install @alpha, which is wrong for the
    second. It is also above the sidebar, far from where someone arriving from a
    search result starts reading.

    Deliberately a separate axis from navTag. That one says when something
    changed
    and decays on a timer — grey at 30 days, gone at 60 — while
    availability stops being true only when a release makes it false, and a page
    can be both new and unreleased at once. Where both apply the sidebar shows
    availability, because in a column that narrow it is the more useful of the
    two.

    Applied to the six pages documenting components that do not exist in 1.8.5:
    Book, Effect, InfoTip, NavItem/NavGroup, Scrubber and Setting,
    verified against the published dist-tags rather than assumed. The media
    players page is left alone on purpose — its video half shipped in 1.8.5 and
    only the audio player is new, so the page as a whole is not alpha-only.

  • Feedback takes an icon. It was locked to the variant's glyph, with
    showIcon only able to turn it off. icon accepts any IconName, so a
    custom name registered through IconLibraryOverrides works here too; leaving
    it out keeps the variant's icon.

  • **Placement, Side and Alignment are exported from the pac...

Read more

v2.0.0-alpha.1

v2.0.0-alpha.1 Pre-release
Pre-release

Choose a tag to compare

@lorant-one lorant-one released this 25 Sep 09:09
582c771

[2.0.0-alpha.1] — 2026-09-17

The second alpha preview, on the same alpha dist-tag. npm install @once-ui-system/core still resolves the 1.8 line; asking for this one stays
deliberate:

npm i @once-ui-system/core@alpha

Mostly regressions the first alpha shipped and the first alpha found — which
is what an alpha is for.

Fixed

  • CountFx no longer freezes when value reverts mid-animation. A run
    started from the last completed value, which was recorded only when an
    animation reached its end. Changing value back while one was still in
    flight therefore hit the value === previousValueRef.current early return
    after the effect cleanup had already cancelled the frame: nothing was left
    driving the number, and the display stayed on whatever intermediate value it
    happened to be showing until some third value came along. A monthly/annual
    pricing toggle clicked twice in quick succession was enough to trigger it.
    Runs now start from whatever is on screen rather than from a completion
    guarded ref, so an interrupted transition reverses from where it got to and
    always settles exactly on the target. The smooth effect renders its digit
    wheels from the same origin, which now tracks the run in flight instead of
    the last one that finished. Reported and diagnosed by @chanderlud in
    #134.
  • DatePicker and Carousel rendered invisible, but interactive, for four
    minutes.
    The 2.0 pass that moved RevealFx.delay to milliseconds also
    multiplied the two speed values in core by a thousand — and speed was
    already milliseconds, so the calendar's reveal became a 250-second opacity
    transition and the carousel slide's a 300-second one. The buttons were
    mounted, focusable and clickable underneath; you just could not see them.
    Both are back to 250 and 300, and a test fails on any core component that
    asks RevealFx for a reveal slower than five seconds.
  • Table's page-size select was a 2rem sliver, User, UserMenu, OgCard
    and Avatar skeletons filled their container.
    2.0 renamed height to
    size on the field components and replaced Skeleton's own width /
    height scale with size, and the codemod applied that to consumer trees —
    but core's own call sites were never run through it. height="xs" on the
    pagination Select and the table search, height="m" on the User
    skeleton and four on OgCard's all landed on the Flex layout prop of the
    same name, where xs and m are spacing tokens; Avatar's loading circle
    passed width / height tokens instead of size. All are on the 2.0 names,
    the page-size select gets 5rem to show its value, and a test scans every
    component for the 1.8 spellings.
  • OgCard showed a loading skeleton over data it was handed. With both
    url and ogData set, the fetch for the URL still ran and loading stayed
    true until it finished, so the mock-data card drew skeletons over content it
    could already render. Provided data is never fetched for now, and never
    loading.
  • The time picker's header showed 13:14 PM. It printed the 24-hour value
    and then appended the meridiem; it prints 01:14 PM now.
  • The published package now carries its license text. package.json has
    declared "license": "MIT" all along, which is what license scanners read,
    but the grant itself never shipped: the MIT file lived only at the monorepo
    root, and npm includes a license only when it finds one in the package
    directory. No tarball up to and including 2.0.0-alpha.0 carried one. The
    notice also now names Dopler, the entity, rather than Once UI, the product.

Fixed (docs site, not published code)

  • Markdown tables rendered as raw pipes. The docs pipeline had no GFM, so
    the harness table on the AI coding page and the clamp table on the Input page
    printed their source. remark-gfm is in the pipeline now and a markdown
    table renders through the design system's Table, like every other table in
    the docs. Two harness links pointed at the wrong files on the way.
  • The IconButton custom-content example lost its label on the primary
    button.
    The Text inside forced onSolid="neutral-medium", a neutral
    on-solid token over a brand solid, which is near-white on the docs' contrast
    solids. A button already sets the text colour for its own surface, so the
    example inherits it.
  • The "Copy for LLM" menu's three options carry prefix icons (document,
    OpenAI, Claude). The two brand marks come from react-icons, registered in
    the docs app's own icon library as the harness rules prescribe.
  • The Option prefix example rendered nothing: it passed icon to Icon,
    whose prop is name.

Changed

  • Agent guidance: a prop another prop already implies is a default too. The
    harness told agents to omit position="relative" and then shipped examples
    writing fillWidth beside maxWidth (which already fills the width),
    minWidth={0} beside fillWidth (which already sets min-width: 0), and
    border="neutral-alpha-weak" where bare border draws the default border —
    the surface recipe itself was written that way, in the rules, the compact
    rules, a task bundle, AGENTS.md and 15 example files. All of them now use
    the short forms, and the rules gain the principle behind them plus one on
    zIndex: it belongs to an isolated stacking group (a decorative layer and
    its content sibling, a sticky header over its pane), not to a lone element
    or a page-wide ladder.

    validate-ai-code enforces the three mechanically, per element rather than
    per file, with --fix for each; it also flags a zIndex in a file with
    nothing positioned absolute, fixed or sticky. Tag scanning is brace-aware
    now, so a > inside an arrow function in a prop no longer ends the tag
    early. Covered by tests.

v.1.7.0

Choose a tag to compare

@lorant-one lorant-one released this 06 May 06:38

Once UI 1.7: Form with Intent

Dependencies
Recharts, PrismJS, CompressorJS and Sharp are now optional dependencies. Please install them according to your needs when migrating to the 1.7 version.

New components

Component improvements

Accessibility improvements

Once UI 1.7 marks a milestone: our first truly community-driven release. From bug reports to feature contributions, the people using Once UI every day helped shape what it became.

Thank you to everyone who contributed code, filed issues, or shared feedback. This release is better because of you.

shaayar
JExcellence
chanderlud
divyanshudhruv
hitomihiumi
bugsNburgers

v1.6.0

Choose a tag to compare

@lorant-one lorant-one released this 03 Feb 15:30

Once UI 1.6

Feature

Flex + Grid

  • width, minWidth, maxWidth, height, minHeight, maxHeight props support %, vh, dvh, vw and calc values and apply them as inline style
  • translateX and translateY prop (supports number as REM, string as SpacingToken, %, vh, dvh, vw and calc)
  • paddings, margins and gaps support numbers as REM
  • boolean border shorthand prop (border, borderLeft ... borderX ...): adds 1px solid default border with a new CSS class and CSS variable (aliasing neutral-medium)

ScrollLock

  • new utility component

ThemeInit

  • new utility component to centralize FAUC logic

Heading, Text

  • family prop: override default font family of a variant

Fix, Refact

Flex + Grid

  • breakpoint object cascading with inline styles (larger breakpoint styles will cascade to smaller if not set)
  • zIndex and borderWidth props accept string

DropdownWrapper, Dialog, ContextMenu

  • use new ScrollLock component

Dialog

  • fix issue when sometimes Dialog opened as inert, breaking interactivity

UI

Textarea

  • styled scrollbar

LineChart

  • remove left legend padding when using ReverseY prop

v1.5.3

Choose a tag to compare

@lorant-one lorant-one released this 16 Nov 19:07
ab33eb0

Once UI 1.5.3

Features

  • Bulge + WeatherFx custom triggers
  • Zoom cursor on Media enlarge and improved image rendering
  • MegaMenu transition refined
  • Chart axis reverse positions
  • ShineFx inverse prop for inverting effect
  • "sharp" shape added to StylePanel
  • custom icon in Avatar

New

  • CelebrationFx: for confetti and fireworks
  • RadialGauge and LinearGauge components for meters

Fixes

  • add default minHeight to charts instead of height

v1.5.0

Choose a tag to compare

@lorant-one lorant-one released this 19 Oct 17:14

Once UI 1.5 — Curiosity in Code

Accessibility Improvements

DropdownWrapper

  • Scroll lock when open
  • Better hover + arrow key handling when using simultaneously

EmojiPicker

  • Refactored scroll behavior
  • Improved performance

Component Improvements

Flex

  • Custom breakpoint support (now a client component applying props and styles accordingly)
  • Hidden elements are no longer removed from the DOM — only classes are applied

Scrollbar

  • Allow default scrollbar
  • Improved minimal scrollbar UI

Radius

  • "full" is now supported for individual border radius (e.g. topLeftRadius="full")

Button

  • New shorthand prop rounded using data-border

Background

  • Improved line rendering for better visual quality

Card

  • More accurate border radius spreading for cleaner tab selection UI
  • Removed hover effect on tap for more natural responsive design

Input / Textarea

  • Native character count support for maxLength

MegaMenu

  • Improved animations
  • Support for custom content via ReactNode
  • Reduced re-rendering

Icon / IconButton / HeadingLink

  • Refactored with new Animation component
  • Added fade-in animation for Tooltip

HeadingNav

  • Added default heading

Heading / Text

  • Adjusted line-height for display sizes
  • Separate multiplier variables for display
  • Added support for XS and XL label sizes

TiltFx

  • Added distortion amount support

UserMenu

  • Disabled click when loading

New Components

Hover

A minimal component that supports overlaying elements on hover.

Animation

A robust component that can be used to craft more complex interactions.

HoverCard

A portal-based element built on top of Animation for simple hover interactions.

Timeline

A static timeline component that supports various data elements.

Swiper

A new multi-image component that supports mobile gestures and dragging.

TypeFx

A typewriter-style component that supports an array of words.

MatrixFx

A canvas-based animation for dot patterns with style adjustments.

ShineFx

A shining text effect, recently used for AI "Thinking" states.