Changelog
Notable changes to @colorffy/css and @colorffy/ui, newest first. Anything
that needs you to edit your own markup is written up in full in the
migration guide — those entries link there instead of
repeating it.
3.0.0
Added
- Density modes.
data-density="compact | comfortable | spacious"on<html>or any element tightens or loosens the spacing and the height of fields and buttons inside it; the nearest one wins, and touch screens keep comfortable heights incompact. Where container style queries are supported, the--cffy-densitycustom property selects a mode from CSS as well. Two new token scales carry the heights,--cffy-field-height-sm|md|lgand--cffy-control-height-sm|md|lg, and the input, button and calendar day hooks default to them;comfortablematches the previous sizes. See Density. - Spacing tokens for the large steps.
--cffy-space-28,-36and-40(behindgap-7,gap-9andgap-10) and--cffy-space-72,-96,-120,-144and-192(behind spacing steps6–10) join the scale. They follow the unit and the density like the other steps, and custom CSS can use them too. - Navbar CSS variables.
.navbarreads 25 public variables, from--cffy-navbar-bg-colorand--cffy-navbar-radiusto the link states (--cffy-navbar-link-hover-color,--cffy-navbar-link-active-bg-color, …). Set them on:rootor on a wrapper to restyle every navbar inside it;nav-island,nav-transparentand the stuck state keep their own values. See Navbar → CSS variables. - Popover menu CSS variables.
.popover-menu-containerreads 16 public variables for the panel (--cffy-popover-menu-bg-color,-radius,-shadow,-min-width, …) and its items (--cffy-popover-menu-item-color,-hover-*,-active-*). Destructive items keep their danger color. See Popover menu → CSS variables. - Dropdown CSS variables. Every dropdown panel reads 17 public variables
(
--cffy-dropdown-bg-color,-radius,-min-width, …) and its items (--cffy-dropdown-item-color,-hover-*,-active-*, padding and radius). The easing and duration hooks also reach the mobile bottom sheet, whose variant no longer repeats the base timing. See Button menu → CSS variables. - Dialog CSS variables. Dialogs read 18 public variables
(
--cffy-dialog-bg-color,-radius,-padding,-backdrop-color,-backdrop-blur, title, subtitle and icon colors, …). The easing and duration now reach modals and side sheets: they re-declared the same overshoot curve and 600ms over a base easing that never ran, and the side sheet repeated the base radius, so those duplicates are gone. See Modal → CSS variables. - Sidebar CSS variables. The drawer reads 19 public variables
(
--cffy-sidebar-bg-color,-radius,-padding, item colors and their active state, the overlay color and blur, …). See Sidebar → CSS variables. - Tabs CSS variables.
UiTabsreads 14 public variables: the underline indicator, the border, tab text and its hover and active states, and--cffy-tabs-pills-*for pill tabs (container, radius, raised indicator). Pill tabs no longer repeat the base active color, so--cffy-tabs-link-active-colorreaches them too. See Tabs → CSS variables. - Segmented control CSS variables.
UiSegmentedControlsreads 11 public variables for its frame, items and sliding pill. See Segmented controls → CSS variables. - Footer CSS variables.
UiFooterreads 14 public variables for its colors, padding, the space above it (now independent of the padding), the title, links and icons. See Layout → Footer CSS variables. - Progress, spinner and skeleton CSS variables.
UiProgressBarreads--cffy-progress-bg-color,-color,-label-color,-height,-radius,-font-size,-easing,-durationand-value(the fill, which thevalueprop sets and which replaces the private--_progress-width; see Migration); the spinner reads--cffy-progress-spinner-colorand--cffy-progress-spinner-size, whichUiProgressSpinner'ssizeprop sets; skeletons read--cffy-skeleton-colorand--cffy-skeleton-radius(the shimmer follows the color); the shapes loader reads--cffy-loading-shapes-color. See Progress and Skeleton. - Alert CSS variables. Alerts read 18 public variables: colors, radius,
type, padding and gaps, the title, and
--cffy-alert-snackbar-*/--cffy-alert-toast-*for snackbars. Alerts with an icon no longer repeat the base gap, so--cffy-alert-gapreaches them. See Alert → CSS variables. - Card CSS variables. Cards read 13 public variables for their surface,
border, radius, padding, title and text, plus the selected border of
selectable and link cards. The header and footer corners
now follow the card radius instead of the compiled
$card-border-radius. See Card → CSS variables. - Button CSS variables. Buttons read
--cffy-btn-radius,-height,-padding-inline,-padding-block,-font-size,-font-weight,-icon-size,-shadow,-easingand-duration, shared by every variant; sizes, icon buttons, FABs androundedkeep their own values. See Button → CSS variables. - Chip CSS variables. Chips read
--cffy-chip-bg-color,-color,-border-colorand-radius; selected chips keep the theme's tonal colors. See Chip → CSS variables. - Button group, chip group, FAB group and toggle CSS variables.
--cffy-btn-group-gap,--cffy-chip-group-gap,--cffy-fab-group-offset-block/-inline/-gap, and--cffy-toggle-btn-*forUiButtonToggleGroup(surface, border, radius, padding, subtitle and the hover and selected outlines). The selected toggle's inset ring now follows its border color. See Button group → CSS variables. - Badge CSS variables. Badges read
--cffy-badge-bg-color,-color,-border-color,-radius,-font-size,-font-weight,-padding-block,-padding-inlineand-icon-size, and badge groups--cffy-badge-group-gap. See Badge → CSS variables. - Accordion CSS variables. Accordions read 13 public variables for their surface, radius (the outer corners of a stacked group), padding, title and body text, icons, hover state, motion and the gap between items. See Accordion → CSS variables.
- Avatar CSS variables. Avatars read 17 public variables for their radius, motion, initials tint and text, group overlap and ring, and the status dot's size, ring and colors. See Avatar → CSS variables.
- List CSS variables. Lists read 26 public variables for the item surface, text, corners, padding, icon tile, image, active state, trailing arrow and motion. See List → CSS variables.
- Popover CSS variables. Popovers read 15 public variables for the panel surface, border, radius, shadow, size limits, spacing, offset from the trigger, header text and motion. See Popover → CSS variables.
- Tooltip CSS variables. Tooltips read
--cffy-tooltip-bg-color,-color,-padding,-radius,-shadow,-font-size,-font-weight,-kbd-bg-colorand-kbd-color. See Tooltip → CSS variables. - Breadcrumb CSS variables. Breadcrumbs read 12 public variables for the
gap, link, hover and current-page colors, the current label's weight and
maximum width, separators, text and icon size, and motion. Icon separators
now follow
--cffy-breadcrumb-separator-color. See Breadcrumb → CSS variables. - Stepper CSS variables. Steppers read 24 public variables for the indicator size, corners, text and pending, current and completed colors, the connector, labels, descriptions, the vertical gap and motion. See Stepper → CSS variables.
- Timeline CSS variables. Timelines read 18 public variables for the gap, item spacing, connector, dot, icon and image markers, title, text and timestamp. See Timeline → CSS variables.
- Table CSS variables.
.table(andUiDatatable) reads 20 public variables for the surface, borders, cell padding, text, header, icons, selected and striped rows, the row hover motion, the sticky-header height and the pinned column..td-textnow follows--cffy-table-font-size. See Datatable → CSS variables. - Input CSS variables. Text inputs, textareas, selects and the color
picker's field read
--cffy-input-*variables for their size, padding, radius, surface, border, text, placeholder, shadow, hover, focus and invalid states, label and motion;--cffy-form-group-spacingsets the space below each field. Ranges read--cffy-input-range-*and the file dropbox--cffy-input-file-*. A range's thumb now stays centered on the track when either size changes. See Inputs → CSS variables. - Checkbox, switch and radio CSS variables.
.form-checkreads--cffy-input-check-*variables for the box size, gap, spacing, surface, border, checked color, checkbox radius and motion, and switches--cffy-input-switch-*for the track and thumb. See Inputs → Checkbox, switch and radio CSS variables. - Prefix and suffix CSS variables. Input adornments follow the field's
--cffy-input-radius,--cffy-input-border-*and--cffy-input-bg-color, and read--cffy-input-group-bg-color,-color,-padding-inline,-font-sizeand-icon-size. See Inputs → Prefix and suffix CSS variables. - OTP CSS variables.
.form-otpreads--cffy-input-otp-gap,--cffy-input-otp-font-sizeand--cffy-input-otp-font-weight; the boxes follow the other--cffy-input-*variables. See Inputs → OTP CSS variables. - Divider CSS variables. Dividers read
--cffy-divider-thickness,-color,-spacing,-inset,-text-gap,-text-colorand-text-font-size. See Divider → CSS variables. - Carousel CSS variables. The
.carouselscroll buttons (native::scroll-button()and the.carousel-btnfallback) read--cffy-carousel-btn-size(default44px),--cffy-carousel-btn-bg-color(--cffy-surface-body-pane),--cffy-carousel-btn-color(--cffy-on-background),--cffy-carousel-btn-shadow(--cffy-shadow-sm) and--cffy-carousel-btn-radius(--cffy-radius-full). - Header, hero and subheading CSS variables.
UiHeaderContentreads--cffy-header-*(spacing, gap, title and description),UiHeroContent--cffy-hero-*(gaps, spacing, line length, description and the actions' offset and gap) andUiSubheadingContent--cffy-subheading-gapand--cffy-subheading-actions-gap. See Layout → Header CSS variables. - Navigation bar CSS variables. The bottom navigation bar reads
--cffy-navigation-bar-bg-color,-shadow,-item-color,-item-active-color,-item-hover-color,-font-size,-icon-size,-indicator-color,-indicator-radius,-easingand-duration. See Navigation bar → CSS variables. - Link, code and kbd CSS variables. Base links read
--cffy-link-color,-hover-color,-active-color,-underline-color,-easingand-duration;<code>reads--cffy-code-colorand--cffy-code-font-size; and<kbd>reads--cffy-kbd-bg-color,-color,-font-size,-radiusand-shadow. See Typography → CSS variables. - Icon wrap CSS variables.
.icon-wrapreads--cffy-icon-wrap-padding(default20px),--cffy-icon-wrap-radius(--cffy-radius-full),--cffy-icon-wrap-bg-color(--cffy-background),--cffy-icon-wrap-color(--cffy-primary-a10) and--cffy-icon-wrap-tint-alpha(10%, the strength of the colored variants' tint). Sizes keep their own padding and corners, and colored variants their own colors. - Icon-only tabs.
UiTabstakesiconOnly: tabs with aniconrender as square icon buttons (.tabs-icon-only), with the label kept as visually hidden text for screen readers and a nativetitletooltip. See Tabs → Icon-only tabs. - State layer tokens.
--cffy-state-layer-color(default--cffy-primary-base),--cffy-state-hover-opacity(8%) and--cffy-state-pressed-opacity(12%) set the hover and pressed background of every interactive surface in one place. See Customization → Hover and pressed states. - Container tokens for tinted surfaces. For primary, secondary, accent,
success, warning, danger, info and muted,
--cffy-<color>-containeris the tinted surface (a pale tint in light mode, a dark one in dark mode) and--cffy-on-<color>-containerthe text and icon color on it, so a custom tonal block takes two tokens. Tonal buttons, badges and alerts paint the pair; the.text-*-emphasisutilities and the active navbar, popover-menu and list states read the text token.--cffy-on-primary-containerexisted before as plain black or white and was unused; it now carries the primary tint. The.bg-<color>-containerand.text-on-<color>-containerutilities apply them. See Tonal tokens. UiButtonMenuItemkeepOpen. Items now close their menu when clicked (see Fixed); setkeep-openon an item that toggles a setting or opens a further step so the menu stays put.UiInputPassword,UiInputSearchandUiInputTags. A password field with a show/hide toggle (v-model:revealed), a search field with a clear button that emitssearchon Enter andclearon clear or Esc, and a tags field that keeps astring[](Enter or a separator adds, Backspace removes, pasted lines split,maxcaps the count, and a duplicate or a tag pastmaxis announced and emitted asreject). They take the shared input props. See Inputs.- Inline input adornments.
UiInputTexttakesadornments="inline", which puts the#prefixand#suffixslots inside the field (.input-group-inline) for icons and icon buttons, andautocomplete, which reaches the field instead of the wrapper. The tags field is.form-tags, which reads the--cffy-input-*variables plus--cffy-input-tags-gap. - Inverse color tokens.
--cffy-<color>-inverse(primary, secondary, accent, success, warning, danger, info and muted) is the color to use on an inverse surface such as a toast, snackbar or tooltip, which is dark in light mode and light in dark mode. It takes the light brand tone in light mode and the dark one in dark mode, kept light or dark enough to read on any brand color; browsers without relative color syntax use the plain brand tones. The.text-<color>-inverseutilities apply them, and theinverse-color()SCSS function builds one. See Tonal tokens. - Container width scale.
--cffy-container-3xs(16rem) to--cffy-container-7xl(80rem) give component widths one t-shirt scale, andmax-w-{size}utilities read the same steps. The sidebar, popovers, popover menu, toasts and dialogs take their widths from it, so one token resizes everything on that step. See Customization → Container widths. - Utility columns in
UiDatatable. Columns takehideLabel, which keeps the label for screen readers but hides it in the header,hideable: false, which keeps the column on screen and out of the column manager, andfit, which sizes it to its content through the new.table-col-fitclass. A column with an emptylabelis no longer listed in the column manager as a blank row, and an always-visible column no longer counts toward the one column the manager keeps on screen. Thecolumn-managerandcolumn-toggleslots receive only the hideable columns. See Datatable. UiPagination. Page buttons in a<nav>, bound withv-model:page, sized fromtotalandpageSizeor fromtotalPages. Long ranges collapse around the current page (siblingCount) with a fixed number of buttons, and below 600px, or withcompact, the numbers give way toPage 3 of 12.showEdgesadds first and last buttons,labelstranslates the text, and page moves are announced. The.paginationclasses read--cffy-pagination-gap,-radius,-current-bg-color,-current-colorand-status-color. See Pagination.- Datatable pagination.
UiDatatabletakespagination: { pageSize }andv-model:page: it sorts every row, shows one page and renders aUiPaginationunder the table. Select-all covers the current page, and the page goes back to 1 when the sort, the page size or the row count changes. See Datatable. UiInputCombobox. A field that picks one value from a list you can search, a native replacement for a filterable select. It takesoptions,optionLabelandoptionValuelikeUiInputSelect, plusoptionGroupfor headings,optionDisabled,clearableand an#optionslot. Typing filters by label, ignoring case and accents;:filterable="false"makes it select-only with type-to-jump. The keyboard follows the WAI-ARIA combobox pattern, with focus kept in the field. The list is apopoverplaced with CSS anchor positioning, so it opens over dialogs and outside clipping containers and flips above the field when needed; browsers without anchor positioning get the same placement from a small script. It's styled by the new.listbox-popup/.listbox-optionclasses and--cffy-listbox-*variables, and.input-group-inlinenow fits a second suffix. Withremotethe server filters: typing emitssearchafter a pause (searchDelay,minSearchLength),loadingshows a searching row, results are announced, and a picked option keeps its label across searches.freeTextkeeps typed text that isn't an option. See Inputs → Combobox.UiInputMultiSelect. The same searchable list for several values, a native replacement for a multi-select. The model is an array in pick order; the field shows a removable chip per value likeUiInputTags(Backspace removes the last), and pastmaxChipsvalues amaxChipsLabelsummary such as "5 selected" instead, so the field keeps to one row. The list stays open while you pick,maxdisables the other options once it's reached, and it emitsaddandremove. It takes the sameremotesearch, andfreeTextadds typed text through an "Add “…”" row (.listbox-option-create,--cffy-listbox-create-color). See Inputs → Multi-select.UiCalendar. An inline month grid that picks one date, several dates or a range (mode), withDatevalues at midnight local time. It shows one or more months side by side (months), honorsmin,maxand adisabledDatescallback, and takes month and weekday names and the first day of the week from the locale throughIntl. A pending range follows the pointer,v-model:monthcontrols the visible month,fluidfills the container's width like a mobile app calendar, and the#dayslot adds content such as dots. Keyboard navigation follows the WAI-ARIA date grid (arrows, Home/End, Page Up/Down, Shift for years). The.calendarclasses read 15--cffy-calendar-*variables, and days take the control shape. See Calendar.UiInputDate. A date field with a calendar popup, a native replacement for PrimeVue's DatePicker. It holds aDate,{ start, end }withmode="range"(written once both ends are picked) or aDate[]withmode="multiple", and accepts dates typed in the locale's numeric order, several separated by commas.presetslists shortcuts built with the newdatePresetshelpers (lastDays(7),lastMonths(6),lastYear(), …),timeadds hour and minute fields (or seconds, withminuteStep) with a Now button,timeOptionslists time slots beside the calendar instead (disabledTimesmarks booked ones),confirmholds the picks until Apply (on by default with times, or for a range with presets), andtrigger="button"shows the matching preset ("Last 7 days") or the dates on a button, with From and To fields in the popup. The popup is anchored like the combobox list, Esc closes it without closing a dialog around it, and below 600px it becomes a bottom sheet with preset chips and stacked months. The.calendar-popupclasses read 10--cffy-calendar-popup-*and--cffy-calendar-preset-*variables. See Inputs → Date.- Shape roles. Components take their corners from three tokens:
--cffy-shape-container(cards, dialogs, popovers, menus, alerts, lists),--cffy-shape-field(inputs, selects, the file drop zone) and--cffy-shape-control(buttons, chips, toggle buttons, sidebar items). Set the three to make the whole interface sharp, soft or pill-shaped at runtime; nested and tall shapes derive from them, and badges and avatars stay round.$shape-container,$shape-fieldand$shape-controlset the defaults in SCSS. See Customization → Shape roles. - Localization.
app.use(ColorffyUI, { locale, labels }), the Nuxt module'scolorffyUI: { locale, labels: 'es' }option orUiConfigProviderset the locale and every text the components write on their own: button names, the "Optional" hint, empty states, screen-reader announcements and date names. English and Spanish ship as@colorffy/ui/locales/enand@colorffy/ui/locales/es,useColorffyConfig()switches language at runtime, and props such asclearLabelstill override one component. Texts with placeholders can also be functions of their values (LabelTemplate), for singular and plural wording such as the Spanish pack's "1 seleccionado". See Localization. - Vue transitions. Pass
fade,slide-up,slide-down,slide-start,slide-end,scaleorlistas thenameof a<Transition>or<TransitionGroup>;listalso moves the items that stay. They share six variables for duration, easing, distance and scale (--cffy-transition-enter-duration,--cffy-transition-distance, …), and reduced motion keeps the fade without the movement. See Effects → Transitions. - Keyboard navigation in menus.
UiButtonMenufollows the WAI-ARIA menu button pattern: ↓ or ↑ on the button opens the menu on its first or last item, ↑ and ↓ move between items and wrap, Home and End jump to the ends, and a letter moves to the next item starting with it. → opens aUiButtonMenuSubmenuon its first item and ← closes it, Tab closes the menu and moves on, and an item picked from the keyboard gives focus back to the button. The panel is arole="menu"of menu items and separators.UiPopoverMenutakes the same arrow, Home, End and letter keys. See Button Menu → Keyboard. UiButtonMenuItemchecked. Makes the item a menu checkbox (role="menuitemcheckbox") and sets its state. The datatable's column manager uses it, so screen readers announce which columns are shown.autofocuson a field in a modal.UiModalfocuses the control of a field component markedautofocus(<UiInputText autofocus>) when it opens. The attribute lands on the component's wrapper, and browsers only autofocus an element that takes focus itself, so the modal opened on its first button instead. See Modal.
Changed
@colorffy/cssdoesn't installfloating-vue. It styles FloatingVue's poppers but doesn't need the package, sofloating-vueis an optional peer and CSS-only projects no longer get it;@colorffy/uistill requires it.@colorffy/uialso declares@nuxt/kit, which its Nuxt module imports, as an optional peer.- Mask shapes use the standard
mask-imageonly. Each shape declared its SVG twice, once for-webkit-mask-image. The unprefixed property has worked in every major browser since December 2023 (Safari 15.4), so the prefixed copies are gone, which halves the masks' minified size; Safari before 15.4 shows the element without the shape. - The sidebar's active-icon color is a public variable. The leading icon of
the current-route item read
--_drawer-active-color, a private variable nothing declared. Set--cffy-sidebar-item-active-icon-colorinstead; see Migration. - Small ranges scale their track and thumb.
size="sm"onUiInputRange(.form-range.form-sm) used to shrink only the field's height, leaving the full-size track and thumb. It now also uses a0.5remtrack and a1.35rem×0.25remthumb. - One hover and pressed background for every interactive surface. Each
component used to pick its own surface token for hover (the card the overlay
surface, list items and dropdown items
container-high, tabs and the sidebarcontainer, table rowssurface-a10, popover menu items a primary tint), so the same gesture looked different from one component to the next and barely showed on some surfaces. List items, selectable and link cards, accordion headers, dropdown and popover-menu items, tabs, chips, table rows, the file dropbox, and sidebar, navbar and menu-tools links now mix their own background with the primary color, the same tint the surface ramp is built from: 8% on hover, 12% while pressed or focused from the keyboard. Navbar, sidebar, popover-menu and menu-tools links gain that pressed state, accordions highlight only while the header itself is hovered, disabled list items no longer react to hover, and the destructive popover-menu item and a file dropbox holding a valid file use a danger or success layer. - Tonal ramps mix in
oklab. Thea10–a90ramps, the surface ramp, the disabled tokens, tonal button, badge and alert backgrounds, and the remaining component mixes blended insrgb. They now mix inoklab, like the state layer and text selection, with the percentages retuned so each token keeps its lightness: nothing moves more than about 0.03 ΔE in OKLab, and the deepest light-mode steps come out a touch darker. Light tonal backgrounds are slightly less saturated, sinceoklabdoesn't add the extra chromasrgbpicks up when mixing toward white. In dark mode--cffy-tonal-dark-intensitynow defaults to58%and--cffy-tonal-light-intensityto8%(were70%and15%). Overridden intensities blend inoklabtoo, so a custom dark-mode value comes out darker than before; compare it against the new defaults. - Dark-mode tonal ramps darken step by step. In dark mode the primary,
secondary and accent ramps darkened from
a10toa80and then jumped back to a light tint ata90, and the success, warning, danger, info and muted ramps mixed toward white from an already light base, so they stayed near-white witha90landing betweena60anda70. Every ramp now darkens from one step to the next in both modes,a90included; the light text tone it used to hold is--cffy-on-<color>-container, which the library now reads wherever it reada90. Filled semantic buttons darken on hover and press instead of lightening, tonal backgrounds and bordered-badge borders come out a step deeper, and--cffy-muted-a10(secondary text) is slightly dimmer. If your own styles reada90as a text color, see Migration. - Text on a brand color follows the brand color.
--cffy-on-<color>was fixed per color (white on primary, secondary, success, danger and info in light mode, black in dark mode, always black on accent and warning), so a light custom color kept white text unless you overrode it. Each one now picks black or white from the lightness of the solid fill the components paint,--cffy-<color>-a10, whichever has more contrast, in both modes. In the default theme, info fills switch to black text (white measured 3.1:1, black measures 6.8:1). Values you set yourself still win. Browsers that can't evaluate the calculation keep the fixed values: those without relative color syntax, and Safari 16.4–17, which implements an older draft of it. - Tonal buttons step from their container. Hover and pressed used to lower
--cffy-tonal-dark-intensityby 10% and 20%, so a tonal button ignored any surface you set. Each state now mixes the container 15% and 30% back toward the fill, which matches the previous colors within ΔE 0.03 and carries an overridden--cffy-<color>-containerthrough every state. For SCSS users,btn-tonal()andcontainer-tonal()now take the surface instead of computing it; see Migration. - Events drop the
onprefix.UiButton,UiButtonMenuandUiButtonTooltipemitclick(with theMouseEvent) instead ofonClick; the inputs emitupdateinstead ofonUpdate;UiModalandUiConfirmModalemitcloseinstead ofonCloseDialog;UiButtonToggleGroupemitsoptionClickinstead ofonOptionClick. Vue addsonitself when it builds a listener, so the old names had to be written@on-click, while a plain@clickonly worked because the native click fell through. A disabled or loading button never emitsclick. See Migration. - Sidebar width hook renamed.
--cffy-nav-drawer-widthis now--cffy-sidebar-width, named like every other component hook; the default is still 18rem. See Migration. - Dialogs and toasts snap to the container scale. Their widths moved to the
nearest step: the confirm dialog tops out at 24rem (was 25rem),
smat 28rem (was 26rem),mdat 42rem (was 40rem) andlgat 56rem (was 60rem), with minimums following the step below; the toast minimum is 18rem (was 300px). The sidebar, side sheet, popover and popover menu keep their sizes. UiDatatable's toolbar buttons use the default icon-button shape. The column toggle and column manager were small-radius squares (shape-sm); they now use the same shape as any other icon button in the toolbar.- Tab and step selection works with
v-model.UiTabsandUiSegmentedControlsemitupdate:activeTabinstead ofupdateActiveTab, andUiStepperemitsupdate:activeStepinstead ofupdateActiveStep, sov-model:active-tabandv-model:active-stepkeep the selection in sync. See Migration. idrenders as given. Buttons, cards, accordions and link tooltips prefixed it (button-save), and inputs suffixed it on the field (email-input-text), so the value you passed could not be used for anchors,<label for>, scripts or tests. Theidnow lands unchanged on the main element. See Migration.- Spacing utilities follow the spacing tokens. The margin, padding, flow,
block-spacing, position (
top-*…), translate and gap utilities compiled fixedremvalues, so changing--cffy-space-unitrescaled components but left utilities behind. They now read--cffy-space-*and--cffy-space-unit: same class names and the same values at the default unit, no markup changes.pxstays a 1px hairline. - Empty-state and loading titles use the paired line-height.
UiEmpty,UiLoading,UiShapeLoadingandUiExpressiveLoadingtitles moved from the legacy font-size classes to the t-shirt ones, which pair each size with its line-height, so a 20px title's line box grows from 20–24px to 28px. Icons keep their tight line box. UiSidebar'sheader,bodyandfooterslots render their regions. They used to place content inside.drawer-contentwithout a wrapper, so they only worked when filled with the region components. LikeUiCardandUiModal, each slot now renders inside its own.drawer-header/.drawer-body/.drawer-footer, skipped when empty, withheaderClass/bodyClass/footerClassfor extra classes.- Private variables are named after their hooks. Every
--_*variable fed by a public hook now carries that hook's name (--_card-gutter-x→--_card-padding-inline,--_nav-tabs-link-color→--_tabs-link-color,--_drawer-*→--_sidebar-*,--_form-*→--_input-*), and the--_color-pickerp-widthtypo is fixed. Rendering is unchanged. Private variables were never public API, but if your CSS overrode one, set its public hook instead; see Migration.UiDatatable'sstickyHeightnow sets the public--cffy-table-sticky-max-height. - Every public custom property is namespaced with
--cffy-. Unprefixed names like--space-*and--theme-*could collide with other libraries on the same page. The semantic--theme-*tokens now use the namespace in place oftheme-(--theme-primary-a10→--cffy-primary-a10), and every other public token, component variable, animated@propertyand internal anchor name gains it (--space-16→--cffy-space-16,--card-bg-color→--cffy-card-bg-color). Private--_*variables are unchanged, and the unused$property-prefixSCSS variable is gone. See the migration guide for the find-and-replace. @colorffy/uideclares@colorffy/cssas an optional peer dependency. The components set--cffy-*variables that only@colorffy/css3.x reads, so pairing UI 3.0 with CSS 2.x left them without effect and without a warning.@colorffy/uinow lists@colorffy/css^3.0.0as an optional peer, so package managers warn about a mismatched version and still install UI on its own.- Groups of text buttons sit closer. A
.btn-groupwhose buttons are all.btn-textuses a--cffy-space-4gap instead of the 12px / 8px one. A text button has no visible edge, so its padding already spaces it and the wider gap pushed the icons apart. Text-button toolbars, including the datatable's when its slots andtoolbarButtonuse the text variant, now match the spacing of a hand-built icon row. Groups that mix in any other variant keep the wider gap. - The datatable's column manager stays open while you toggle columns. Each
pick used to close the menu, so hiding three columns took three trips. Its
items now keep the menu open (
keepOpen); click outside or press Esc to close it. - Selected table rows are tinted.
--cffy-table-selected-bg-colordefaulted to the page background, so a selected row looked almost the same as the others in light mode. It now defaults to--cffy-primary-container. - Radius settings reach the related parts. With the shape roles, a
$form-border-radiusyou set also shapes the file drop zone, input-group adornments and checkboxes (half the field radius), and$button-border-radiusshapes the floating action button (twice the button radius). They used fixed radii before. The defaults look the same. - Menu buttons have no default tooltip.
UiButtonMenu'stooltipTextdefaulted to'menu', so every menu button without one showed a lowercase English "menu" tooltip, text buttons included. The tooltip now only shows when you passtooltip-text, and an icon-only trigger without atitleor tooltip is named by the newbuttonMenu.ariaLabellabel ("Menu", "Menú"). See the migration guide. - Every part follows the shape roles. A few parts kept a fixed step of the
radius scale, so a sharp or pill theme left them behind. Navbar links, tab
links,
<kbd>, breadcrumb links and the navbar initials now take--cffy-shape-control; tables,<details>, list and timeline icons and images, icon tiles and the headless dialog icon follow--cffy-shape-container, never rounder than before; skeletons follow the field role; and the mobile dropdown sheet and the navigation drawer round at twice the container radius, like bottom sheets. Icon buttons, carousel arrows, the alert close and chip remove buttons, progress bars, the navigation bar indicator and the ends of connected button groups stay round unless--cffy-shape-controlis0, and the pill tabs track no longer keeps rounded corners around square pills. With the default roles everything looks the same; the dropdown sheet and drawer round at 24px instead of 25px. - The shape loader's angle is
--cffy-shape-loading-angle. It was--cffy-shape-angle, which now reads like a shape role. - Toggle buttons are named by their content.
UiButtonToggleGroupnamed each option "Pro (selected)", repeating the state its radio role already announces, and its badge "Tier: Save 20%". Options now take their name from their title, text and badge. - The dark-mode danger color is a clear red.
--cffy-color-brand-danger-50, the danger base in dark mode, wasoklch(90% 0.04 12), a pale pink that error text and borders barely set apart from white. It's nowoklch(80% 0.11 20), about 7:1 against dialogs and 9:1 against the page. - Active navbar links keep their size. The current-page link widened its
inline padding to
--cffy-space-16and turned extra bold, which pushed the links beside it. It now only changes its background and color.
Removed
--cffy-on-primary-inverse,--cffy-on-secondary-inverseand--cffy-on-accent-inverse. They held the same value as--cffy-on-backgroundand nothing in the library read them. See Migration.- Legacy font-size tokens and classes. The ordinal
--fs-100…--fs-600,--fs-sm-100…--fs-sm-500and--fs-xl-100tokens and their.fs-*classes, deprecated since 2.5, are gone. Use the t-shirt scale (--cffy-fs-4xs…--cffy-fs-5xl,.fs-base, …); the t-shirt classes also set the paired line-height. See the rename table. - Deprecated SCSS variables.
$space-1/$space-2/$space-3(usevar(--cffy-space-16/32/48)) and the color and font variables deprecated in 2.8 ($primary,$secondary,$accent,$success,$warning,$danger,$info,$muted,$primary-colors,$font-*) are gone. Nothing read them; a@forward … with (…)that still sets one now fails to compile, so drop it and set the--cffy-color-brand-*/--cffy-font-*tokens instead. See Migration. ISegmentedTab.position. Ignored since the segmented pill started reading the active tab's real geometry; the field is gone andISegmentedTabis now an alias ofITabItem. Deletepositionfrom your items.UiPopoverMenu's identity props andbody-extraslot.user,avatarUrl,avatarCustomClassandsubtitlerendered nothing since the header became a title plus the close button; put the identity in theheaderslot withUiPopoverMenuUser.body-extrais gone too: to add rows to amenuItemsmenu, render the body yourself in#body. See the migration guide.UiInputFile'slargeprop. Usesize="lg", which every input shares, for the taller dropbox.BaseSkeleton'sisThumbnailprop. Usevariant="thumbnail".UiEmpty's#buttonslot. The legacy fallback inside#actionis gone; rename the slot to#action.UiSidebarHeader,UiSidebarBodyandUiSidebarFooter, and the default slots ofUiSidebarandUiPopoverMenu. Panel components now take their regions through named slots only. Move sidebar regions into#header,#bodyand#footer, and a menu's default-slot content into#body. See the migration guide.UiButtonToggleGroup'sgroupLabelprop. It was declared but never rendered;ariaLabelnames the group. See the migration guide.UiButton'stooltipTextandplacementprops. They came from the props every button shares, butUiButtonnever showed a tooltip. UseUiButtonTooltip; it,UiButtonMenuandUiLinkTooltipkeep both. See the migration guide.UiPopoverMenu'sauthprop. It was declared but never read. See the migration guide.UiModal'sisHeadlessprop. It was declared but never read;mode="headless"gives the headless layout. See the migration guide.UiDatatable'sskeletonCols,skeletonColExpandedandisExpandedprops. The loading skeleton always drew one cell per visible column and ignoredskeletonCols, whileisExpandedswapped inskeletonColExpandedcells that no longer matched the header. The skeleton now always matches the visible columns;skeletonRowsstill sets the row count.UiTableSkeletonkeeps these props for standalone use. See the migration guide.- The
.menu-toolsmega menu..menu-tools-containerand its tabs, tool grid and call-to-action cards served one app's navigation, and no component rendered them. They are gone, along with.navbar:has(.menu-tools-visible). See the migration guide. .pwa-alert-container. The PWA install/offline prompt (.pwa-alert-container,.pwa-content,.pwa-content-text-wrapper,.pwa-actions) served one app and no component rendered it. Build the prompt fromUiAlertwithtype="snackbar"and actions. See the migration guide..segmented-controland.ripple. The legacy two-segment.segmented-control(UiSegmentedControlsuses.tab-segmented-control) and.ripple,.ripple-dark,.ripple-primaryand.ripple-secondaryare gone, along with_button-effects.scss. Theripple-color()mixin stays. See the migration guide.- The PrimeVue skin.
scss/components/_prime.scss, which styled PrimeVue's MultiSelect, Select, ColorPicker, Toast, ConfirmDialog and InputSwitch and set its--p-*tokens, is gone.UiInputCombobox,UiInputMultiSelectandUiInputDatereplace Select, MultiSelect, AutoComplete and DatePicker; see the PrimeVue migration. - Unused helpers. Nothing in Colorffy UI used these:
.hover-rotate,.hover-scaleand.vr..placeholder-listand.placeholder-loader, whoseplaceholderLoadinganimation never existed.- The
pulse,glitch,shake-x,shake-y,fade-in-left,slide-in-down,spin-wheel,animate-wheel,shine-light,dots-scrollandanimate-gradient-shape-*keyframes. - The
slide,slide-inline,fade-inline,tableandtable-td-fadetransitions, replaced by the new transitions (slide-blockstays). - The spinning icon on
.nav-linkhover. - The custom color-picker popover styles (
.color-picker-popper,.color-swatch,.form-color-picker-wrapper).UiInputColorPickeruses the native color input. layout/_grid.scss, a copy oflayout/_flex-container.scss; import@colorffy/css/scss/layout/flex-containerinstead.
See the migration guide. - Unused tokens and SCSS API.
--cffy-img-testimonial-filterand-invert,--cffy-vfw-400to-800and--cffy-surface-text-field; the@propertyregistrations of--cffy-color-wheel-radius,--cffy-element-radius,--cffy-dots-scroll-position,--cffy-gradient-shape-*and--cffy-noise-lines; the$dark,$scheme-colorsand$fs-tablevariables; andgenerate-static-gradients(), which matchedgenerate-gradients()(its unused third argument is gone too).
Fixed
- Menus and tooltips work inside modals.
UiButtonMenuand every tooltip (UiTooltip,UiButtonTooltip,UiLinkTooltip, the sidebar dropdown and link tooltips) rendered their popup at the end of<body>.UiModalandUiConfirmModalopen withshowModal(), which puts the dialog on top of the page and makes everything outside it inert, so those popups sat behind the backdrop and ignored clicks: aUiDatatablecolumn manager inside a modal couldn't be opened. Popups now render inside the nearest<dialog>or[popover]with fixed positioning,.dialogand.popoverlet them overflow their box (their body still scrolls), and clicking a menu item no longer counts as a click outside the modal. Colorffy CSS also gives FloatingVue poppers theirtop: 0; left: 0origin, which only FloatingVue's own stylesheet set before, so they're placed correctly without it. - Esc closes a menu without closing the modal around it. The menu didn't
handle Esc, so the key reached the
<dialog>, which closed.UiButtonMenunow takes Esc while it's open, closes, and returns focus to its button. Its button also reportsaria-expandedandaria-controls. UiButtonTooltipno longer announces itself as collapsed. Its optionalariaExpandedprop is a boolean, which Vue reads asfalsewhen it isn't passed, so every tooltip button saidaria-expanded="false". It's now only set when you pass it.- Reopened modals start at the top.
UiModalandUiConfirmModalkept the body's scroll position from the last time they were open, so the first fields could be scrolled out of view. - A modal's close button stays on the title row.
.dialog-headerwrapped its children, so on a phone a title and subtitle wider than the space left pushed the close button onto a line of its own. The header no longer wraps: the title block shrinks and its text wraps, with a--cffy-space-12gap before the button. - Select text stops before the arrow.
.form-selecthad the same inline padding on both sides, so in a narrow field a long option ran under the arrow icon. The end padding now clears the icon at every size:2.25rem, or1.75remwith.form-smand2.75remwith.form-lg. - Datatable captions stay readable on phones. The
<caption>sat inside the table's horizontal scroll, centered across the whole table, so on a narrow screen part of it was cut off.UiDatatablekeeps the caption for screen readers and shows its text below the scroll area as.table-caption, in the muted text color. - The navigation bar's indicator leaves when no item is active. It started
on the first item and only moved to a link with the router's exact-active
class, so on a page outside the bar (
/billing) it stayed on Home, and with plain links andactiveItemit never moved. It now also followsaria-current="page", whichactiveItemsets, and fades out when no item is active. - Tables hide their scrollbar in every browser.
.table-responsivehid its scrollbar only through::-webkit-scrollbar, which Chrome ignores oncescrollbar-widthis set, as the reboot does for every element, so a thin bar showed under tables that scroll sideways. It now also setsscrollbar-width: none. - Selects in tables keep their width. A
.form-selectfills its cell (width: 100%), which gives an automatic table nothing to size the column by, so on a phone a select in a table shrank to a square. Inside.tablea select is now at least as wide as its longest option. --cffy-radius-fullmakes pills and circles at any size. It was50px, so a 150pxavatar-lgwas a rounded square and a pill taller than 100px lost its round ends. It is now9999px. The raw.border-radius-50utility keeps 50px.- Small dialogs fit their content on phones. Below 992px every dialog
became a bottom sheet 75% of the screen tall, including
size="sm", whose content keeps its own height. The form sat at the top of a mostly empty sheet, and a tap on the empty part landed outside the content, which closed the dialog. Small dialogs now grow with their content, up to 75% of the screen. - Tables don't widen the page on phones. The screen-reader label of a
hideLabelcolumn is positioned absolutely, and.table-responsivewasn't a positioned box, so the label escaped the table's horizontal scroll and widened the page..table-responsiveis nowposition: relative. It also hides vertical overflow (except with a sticky header), which removes a scrollbar that a 1px caption rounding left next to some tables. - The indeterminate checkbox shows a dash. There was no
:indeterminatestyle, so the datatable's header checkbox looked unchecked while some rows were selected. It now shows a dash on the checked color. - Pasting a list into
UiInputTagskeeps every value. Only pastes with line breaks were split; a comma-separated line went into the field, wheremaxlengthcut it short before it was split. Pastes that contain the separator are now split too. - Nuxt pages fade in, not only out. The
pageandlayouttransitions styled Vue 2's-enterclass, which Vue 3 renamed-enter-from, so withpageTransition: { name: 'page' }the old page faded out and the new one appeared at once. They now fade both ways. - Esc no longer clears a disabled or read-only
UiInputSearch. - List items animate their background and border. The item transition had
no duration, so hover, pressed and active changes snapped. They now ease over
--cffy-duration-200. UiExpressiveLoadingtitle sizes were inverted.size="sm"used the legacyfs-500(20px), the defaultfs-600(16px), andlgafs-700class that never existed, so it fell back to 15px: the larger the size, the smaller the title. They are now 16px, 20px and 24px.UiGridSkeletonpreviews have their height again. Each card set--fixed-size: 6.25rem, but the.h-fixedutility reads--cffy-h-fixed, so the preview block fell back to its 2.5rem minimum. It now sets--cffy-h-fixed.- Popover menu items no longer restyle what you put in them. The item
colored and resized every
<p>and<i>inside it, so buttons placed in thetrailingslot (a theme switcher, say) lost their own text and icon colors. The rules now apply only to the item's label, leading icon and trailing icon. UiButtonMenuItemcloses its menu. Clicking an item left the dropdown open until you clicked elsewhere, and items inside a submenu left both levels open. A click now closes the whole menu.UiModalrenderstitleandmessage. Both props were declared and documented but never shown. They now fill the header and body when those slots are empty. Clicks outside a closed modal also stopped emitting a close event: the outside-click listener ran whether or not the dialog was open.UiConfirmModalshowsloadingLabeland emitsclose. The confirm button kept its label while loading, and the documented close event was never emitted. Both now work, and outside clicks are ignored while it is closed.UiCardno longer shows its title as a browser tooltip. Thetitleprop was also bound to the root's nativetitleattribute. The card body also stopped re-applying the card color, so a text utility on the card (text-on-primary) now reaches the body.UiAvatarshows the photo when bothsrcandinitialsare set. Initials took priority, so the image never appeared. They are now the fallback for a missing or broken image, as documented.UiButtonTooltiplinks.to,href,asandtypewere accepted but not passed to the button, so a tooltip button couldn't navigate.UiSegmentedControlsrenders tab icons and badges. Its tabs accepticonandbadgelikeUiTabs, but only the label was drawn.UiBadgerenders a<span>. The<div>root made badges invalid inside paragraphs and buttons (tabs, menu items), which broke SSR hydration when a badge sat in a<p>.UiTimelinestyles theneutralvariant. It was accepted but had no marker colors; it now uses the muted container pair.UiLinkTooltiphonorsplacement. The prop was declared but never passed to the tooltip.UiInputSelectplaceholder is in English. The default was "Clic para seleccionar"; it is now "Select an option".UiNavbarNavpositionaligns as documented. From 992px,startpushed its group away from the brand (margin-left: auto) andendpushed away from the far edge (margin-right: auto), so a start and an end group floated toward the middle with a gap after the last item.startnow sits next to the brand andendagainst the far edge. If you used twostartgroups to push everything to the right, make the right-hand groupend.- Prefixed and suffixed inputs line up. An input with a prefix or suffix nests
an
.input-groupinside its.form-group, and both added the bottom margin, so the field took twice the spacing. Inside a navbar item the form margin is now dropped entirely, and the prefix and suffix followvariant="transparent"(no border or background) androunded(full radius) instead of keeping the default boxed look. .grid-main-contentno longer scrolls sideways. Its content column was1fr, which cannot shrink below the widest thing inside it, so a wide table pushed the whole page past the viewport instead of scrolling in its own.table-responsive. The column is nowminmax(0, 1fr).- Alert actions stay beside the text. An alert with a title switched to a single column, which pushed its actions onto a row of their own at the bottom. Actions now sit next to the content and only drop below it when the alert is 450px wide or less.
- The segmented control indicator moves without lagging. The labels
transitioned
font-weight, so with a proportional font the active label kept growing while the indicator travelled toward it, and the indicator trailed behind a moving target. The weight now changes at once. UiEmptycenters its action. Buttons in theactionslot sat at the left edge because they are block-level; the slot is now wrapped in a centeredUiButtonGroup, which also spaces several actions.- List item actions keep their width. The item content took
width: 100%, so thelist-actionslot was squeezed until its button labels wrapped. The content now takes the free space and the actions keep their natural width. - The sidebar rail centers its header. The rail nudged an image or icon logo with a fixed 1rem offset and left anything else at the start edge. The header now centers whatever logo it holds.
UiShapeLoadingsubtitle is readable. It carriedtext-muted, which overrode the label color meant for text on the blob, so the subtitle faded into the shape. It now uses the same color as the title, and both lines are centered on the shape (a shorter subtitle used to sit at the left edge).<kbd>keeps its light-mode background in older browsers.--cffy-surface-kbdcalledcolor-mix()without an interpolation space, which only browsers supporting the newer optional-space syntax accept, so elsewhere the token was invalid and the key rendered unpainted. It now namesoklab, the space those browsers default to, so they render it the same.UiChiprenders itsid. The prop was declared but bound to neither root, so the chip had no id. It now lands unchanged on the chip's root.UiChipGroupno longer passes each option'siddown as a DOM id, so option ids such aslistdon't collide with ids elsewhere on the page.- Dialogs emit
closewhen closed with Esc.UiModalandUiConfirmModalemittedclosefromcloseDialog()only, so pressing Esc closed the native<dialog>without telling the parent. Both now emitclosefrom the dialog's owncloseevent, which coverscloseDialog(), outside clicks and Esc alike. The event now fires once the dialog has closed, a moment aftercloseDialog()returns. UiConfirmModalhonorscloseOnClickOutside. It closed on every outside click whatever the prop said. It now defaults totrue, as onUiModal, andfalsekeeps the dialog open.- A disabled
UiAccordionstays put.disabledonly setaria-disabledon the<details>, so the header still toggled the item and nothing looked disabled. The header now ignores clicks and Enter/Space, leaves the tab order, carriesaria-disabled, and renders at reduced opacity with a not-allowed cursor and no hover layer. UiIconMaterialappliessizeandcolor. Both props were declared but never bound, so the icon always took the surrounding text size and color.sizenow sets the glyph size (xs…xl= 20 … 48px, numbers are px) andcolorits color; left unset, the icon follows the text as before.UiLinkTooltipappliesfluid. The prop was declared but never applied; it now makes the link full width, likeUiButton.- Using the SCSS mixins adds nothing to your CSS.
abstracts/mixinsdeclared eight@propertyrules and wrote its doc comments as CSS comments, so every stylesheet or Vue<style>block that loaded it with@usegot them in its output. The@propertyrules moved tobase/properties, which the main bundle loads once, and the comments are Sass comments now. The compiledcolorffy.min.cssis unchanged. - Snackbar icons and actions are readable in both modes. Variant icons used
--cffy-<color>-base-fixed, the light-mode brand tone, in both modes, so on the dark light-mode snackbar a primary icon measured 1.7:1 and danger 2.8:1, and on the white dark-mode snackbar warning measured 1.7:1, secondary 1.9:1 and info 2.3:1. Text buttons in theactionsslot took the page's primary and text colors and nearly vanished. Icons now use--cffy-<color>-inverse(6.6:1 or more), and the snackbar points--cffy-primary-base,--cffy-on-backgroundand the state layer at their inverse tones, so its actions read at 11:1 or more. UiInputTextrenders a prefix or suffix added after mount. It decided whether the slots existed once, in acomputed, and slots aren't reactive, so a#suffixshown withv-iflater (a clear button, for example) never appeared. The template now reads the slots on every render.- Errors show under inputs with a prefix or suffix. The message follows the
.input-groupinstead of the field, so the rule that styles it as an error never matched: it showed as plain body text. It now gets the error color and size there too. UiProgressBarandUiProgressSpinneraccept string styles. A stringbarStylesorcustomStyleswas joined to the component's own style object, which turned it into[object Object], so the bar lost its fill width and the spinner its size. Both now pass a style array, which Vue merges, so strings and objects apply on top of the component's own values.IToastDisplayincludestitle.UiAlertToastexposes its title, but the type for its template ref left it out, so setting the title beforeshowToast()needed a cast in TypeScript.UiInputColorPickerappliesplaceholderandreadonly. Both props were accepted but never bound, so the hex field showed no placeholder and stayed editable. The hex field now takes both, andreadonlyalso keeps the swatch's picker closed, since a color input ignores thereadonlyattribute.- Corner toasts fit their text and center on phones. The toast container
is a size container (
container-type: inline-size) so the alert inside can adapt to its width, but a corner placement left it without a width, so it measured 0px: every corner toast fell to its 288px minimum and took the stacked narrow layout, icon above the text. On phones it also slid left: the rule that spans the toast across screens up to 576px wide had less specificity than the placement classes, so abottom-righttoast kept its corner insets while gaining the side padding. Corner placements now give the containermin(--cffy-container-lg, 100% - 2 × offset), and the phone rule covers every placement. - The color picker is one field.
UiInputColorPickerwraps a swatch and a hex text field in.form-color-group, but the text field kept its own shadow, focus ring and invalid border, drawn in the field shape inside the group: with pill fields it showed as a second capsule, and an error turned only the text field red. The swatch was fixed at--cffy-radius-mdwhatever the field shape, and shrank when the field was narrow. The group now carries the shadow, the focus ring and the invalid border and ring, the text field draws none of them, the swatch rounds to half the field radius and keeps its size, and the error message gets the error color and size. Thevariantandroundedprops, accepted but never applied, now style the whole field. - Connected button groups keep their shape with pill controls. The ends of
.btn-group-connectedround to half the button's height and the inner corners took--cffy-shape-control. Set to--cffy-radius-full, a side then asked for 9999px against a 32px box, and the spec answer to radii that overflow a side is to scale every corner of that button by the same factor: the pill ends shrank to nothing and the inner corners came out round, so the group looked inside out and its middle icon button turned into a circle. Inner corners now stop at a quarter of the button's height (8px onbtn-sm, 9px at the default size, 12px onbtn-lg), so the radii on a side never overflow it; below that they still follow the shape role.
2.8.0
Added
- Duration tokens.
--duration-100,-150,-200,-250,-300,-400,-500,-600and-800are milliseconds derived from--duration-unit(1ms), and every component transition reads them, so--duration-unit: .8msspeeds up all motion by 20%, globally or for one subtree. Long looping animations (spinners, shimmers) keep their own timing.
Changed
- Ripples, the spinner and the galaxy gradient follow the theme.
ripple-primary/ripple-secondaryand the spinner's color cycle used the compiled$primary…$successcolors, so they ignored your brand tokens and dark mode; they now use--theme-{color}-base. The white and dark ripples use the brand white and black tokens, andgradient-fuchsiastarts and ends on--color-brand-accent-500. - The SCSS color and font variables are deprecated.
$primary,$secondary,$accent,$success,$warning,$danger,$info,$muted,$primary-colorsand$font-*are no longer read anywhere; they still compile so existing@forward … withconfigurations keep building, and they will be removed in 3.0. Set the--color-brand-*and--font-*tokens instead (see customization).
Removed
- Private variables that never did anything:
--_dialog-easing-out,--_dialog-easing-out-duration,--_navigation-bar-easing-out-duration,--_nav-tabs-gutter-y,--_shape-offsetand--_container-gutter-y. They were declared but never read, so overriding them had no effect.
Fixed
- Alerts with an icon stack in narrow containers. Below 350px the alert
set
--_alert-flex-direction: column, but.alert-contentwroteflex-direction: rowdirectly, so the icon never moved above the text. The content now reads the variable and left-aligns when stacked. - Tab colors go through the button variables.
.tab-linksetcolorand the hoverbackgrounddirectly, overriding--_btn-colorand--_btn-bg-color, so setting those had no effect. The rest, hover and active states now set the variables; the rendered colors are unchanged. --_progress-colorsets the progress bar fill, and--_table-bg-color/--_table-colorset the table's background and text color. They were declared but never applied. Their defaults now match what rendered before (transparentandcurrentColorfor the table).
2.7.0
Added
UiTabstakesfitandrounded.fitmakes the tab bar only as wide as its tabs instead of spanning its container (ignored withfluid).roundedmakes pill tabs and their indicator fully rounded; it only applies withpillTabs. Both radii come from one private variable,--_nav-tabs-item-radius.UiAvatarandUiAvatarGrouptake acolorprop. It tints initials and placeholder avatars with any theme color, at the color's fixed tone and 25% opacity, in place of adding abg-*-fixedclass. On the group it applies to every avatar and the "+N" overflow avatar, andavatarsentries can override it. The CSS classes are.avatar-{color}, driven by--_initials-base-colorand--_initials-alpha.UiAvatarandUiAvatarGrouptake avariantprop.transparentis the see-through tint,tonalthe same tint made opaque, andfilledthe full theme color with its on-color text (.avatar-tonal,.avatar-filled).UiAvatardefaults totransparent;UiAvatarGroupdefaults totonal.border-neutral,border-blackandborder-opacity-*.border-neutraluses the body text color, so it flips with dark mode;border-blackmatchestext-black.border-opacity-{0…100}fades any border color toward transparent withcolor-mix()inoklab.- Shape, weight, motion and focus tokens.
--radius-*,--border-width-sm/-md,--fw-400…800,--ease-*and--focus-ring-color/--focus-ring-offsetare CSS custom properties on:root, and every component reads them. Override them at runtime without recompiling; the SCSS variables still set their defaults. See customization.
Changed
- Avatars in a
UiAvatarGroupare opaque. Initials and the "+N" overflow avatar used a 25% transparent background, so the image they overlap showed through. The group now defaults tovariant="tonal", the same tint made opaque; passvariant="transparent"for the previous look. - Easing curves no longer have
cubic-bezier()fallbacks.linear()is supported in every current browser, so the 25@supportsblocks that swapped them in are gone and each component declares its curve once. Browsers withoutlinear()support (before Chrome 113, Firefox 112 and Safari 17.2) now skip those transitions instead of using a simpler curve. - Reduced motion calms the overshooting curves. Under
prefers-reduced-motion: reduce,--ease-spring,--ease-overshoot-softand--ease-bounceresolve to--ease-decelerate, so dialogs, dropdowns, avatars and the rest of the components using them settle without overshooting or bouncing. - Font sizes are in
rem, notpx. The 17 remaining pixel font sizes (body text, buttons, tables, tooltips, badges, icons) render the same at the default browser size and now follow the user's font-size setting. If your site changes the root font size (e.g.html { font-size: 62.5% }), these now scale with it like the spacing and--fs-*tokens already did: body text set to.875remrenders at 8.75px with a 10px root. - Full-height layouts use
dvh. The sidebar, dialogs, the PrimeVue dialog and.pane-contentsize themselves with dynamic viewport units, so mobile browser toolbars no longer push them past the visible area. The.vh-*utilities keepvh. --fs-xsno longer shrinks below--fs-2xs. Its minimum was0.60rem, smaller than--fs-3xsand--fs-2xs, so on narrow screens "xs" text rendered smaller than the steps below it. The minimum is now0.75rem.border-whiteandborder-darkuse the brand color tokens (--color-brand-white-500,--color-brand-dark-500), liketext-whiteandtext-dark, instead of compiled SCSS colors.
Fixed
- Focused inputs are visible in forced-colors mode. Form controls drew
their focus ring with
box-shadowand removed the outline; forced-colors mode (Windows High Contrast) drops box-shadows, so focus disappeared. They now add aHighlightoutline under@media (forced-colors: active). - The progress bar transition always has a duration.
--_progress-easing-durationwas only declared inside thelinear()@supportsblock; it is now part of.progress's defaults. border-animatedglows without a gradient preset. Its glow is aconic-gradient()of--_border-gradient, which only thegradient-{name}classes set, so on its own the gradient was invalid, the background was dropped and nothing showed. It now defaults to the same primary gradient asborder-gradient.border-animatedstops spinning under reduced motion. Its::beforeand::afterranangle-spinwith noprefers-reduced-motioncheck, unlikeborder-gradient-spin. Underprefers-reduced-motion: reducethe animation is nownone, so the glow stays still at its starting angle.
2.6.1
Fixed
- Shimmer skeletons render again.
.skeleton-shimmerbuilt its gradient from--theme-muted-a5, a token that doesn't exist, so the whole background was dropped and the skeleton was invisible. The highlight is now the muted tone at half opacity. - The segmented control's easing variable is spelled correctly. It was
declared as
--_sc_easing-inbut read as--_sc-easing-in, so browsers withoutlinear()support got no transition. - The side-sheet dialog's fallback animation uses an easing curve. Its
animation-timing-functionread--_dialog-easing-duration(a duration) instead of--_dialog-easing-in. - The active menu-tools subtitle in the navbar is primary-colored. It
referenced
--theme-primary-a0, which doesn't exist; it now uses--theme-primary-a10. - The navigation bar's reduced-motion indicator fades with its own easing.
It referenced the undefined
--easing-in/--easing-out; it now uses--_navigation-bar-easing-in/--_navigation-bar-easing-out. - Private variables that were declared but ignored now apply:
--_drawer-radiussets the sidebar's corners. It held the wrong corners and the radius was written directly;drawer-borderedsets it to0.--_dropdown-shadowsets the dropdown panel's shadow.--_table-border-colorsets every table border.--_form-check-input-active-border-color(previously the misspelled--_form-check-input-activeborder-color) sets a checked input's border.
Their defaults match what rendered before, so nothing changes visually unless you override them. .icon-wrapno longer emits an invalid border.--_icon-wrap-border-sizedefaulted tonone, which isn't a width; it is now0.
2.6.0
Added
UiTabstakes asizeprop.size="sm"adds.tabs-sm, which puts the tab buttons back on the default button height, padding, icon size and font size instead of the larger tab defaults.md(or leaving it unset) keeps the current size.- The
btn-sizemixin has anmdstep. It emits the default.btnsizing, so a component that enlarges its buttons can reset them in one line;.tabs-smuses it. UiDatatabletoolbar slots.#actions-startand#actions-endadd your own buttons to the built-in column button group, before and after it.#column-toggleand#column-managerreplace the built-in buttons and receive the column state (columns,allVisible,isVisible,isLocked,toggle,toggleAll, typed asIDatatableColumnSlotProps), so a custom control drives the same visibility logic.UiDatatabletakes atoolbarButtonprop. One object (variant,color,size,customClass,rounded) restyles both built-in toolbar buttons, instead of a prop per button per option.UiDatatabletakes astickyHeightprop. It sets the sticky scroll container's max height (sticky-height="18rem"; a number is read as pixels), replacing the inline--_table-sticky-max-heightstyle binding. The variable still works and still defaults to32rem.
Changed
UiDatatable's column manager is an icon-only button. It shows theview_columnicon with the same outline, small,shape-smstyle as the column toggle beside it;columnManagerTooltipis its tooltip and accessible name.UiInputFilereads the sharedsizeprop.size="lg"renders the taller dropbox. The input already inheritedsizefrom the base input props but ignored it, so the dropbox had its ownlargeboolean;largestill works but is deprecated in favour ofsize="lg".
Removed
UiDatatable'scolumnManagerTextprop. The column manager button has no label anymore; drop the prop, or move the wording tocolumnManagerTooltip.
Fixed
- Text-only tabs are no longer wider on the right.
.tab-linkkept the button grid's two columns and a--space-4gap even with nothing in the second column, adding 4px after the label; a tab with an icon but no badge got the same phantom gap. Tab links now lay out with flex, so the gap only sits between items that exist. As a side effect,fluidtabs with an icon centre the icon, label and badge together instead of pinning the icon to the left edge. UiDatatable's toolbar renders for#controlsalone. It only rendered when a column started hidden orcolumn-managerwas on, so a table using just the#controlsslot showed nothing. Any toolbar slot now renders it.UiDatatabletoolbar ids are unique per table. The column toggle, column manager and each menu item used fixed ids (columns-toggle,column-manager,column-<key>), which collided with two tables on one page. They are now prefixed with a per-instance id.UiButtonTooltipandUiButtonMenuno longer share ids.UiButtonTooltipdefaultedidto'tooltip', so every instance without an id renderedbutton-tooltip;UiButtonMenubuilt-dropdownand-tooltippopper ids from an empty id. Both now default to no id and leave the popper id to floating-vue unless you pass one, likeUiLinkTooltip.UiDatatable's default empty-state subtitle reads correctly. "Try may want to try…" is now "You may want to try…".
2.5.1
Added
UiFooter,UiFooterGroupandUiFooterItem. The footer styles existed in@colorffy/csswith nothing rendering them; these cover the brand block, the link groups (column or row) and the links themselves, including the plain.anchor-linkentry for a group member with no target.UiFootersupplies its own container and takes the samefluidprop asUiNavbar, plus a#bottomslot for the divided bar under the groups;UiFooterGrouptakesoverlinefor a small uppercase heading.UiHeroContent. A page-opening hero: eyebrow, display-sized title, description and a#actionsslot for CTAs, withsizeselecting the display step andalignmoving the whole block.UiSubheadingContent. A section subheading with an optional description, built on.text-subheading, withasto keep the heading order valid, agutterprop for the space below it, and an#actionsslot for a badge or control beside it.
Changed
.text-subheadinguses the type tokens. It picks up--fs-lg--line-heightand the--space-*gutters, and no longer runs thetext-variablemixin: it sets--font-primarydirectly, which every font honours.- Chips split the container from the selected color.
.btn-chipis the outline container and.chip-elevatedthe raised one; the color classes (.chip-primary,.chip-secondary,.chip-neutral) only apply together with.chip-active..a40and.a60are removed, and.chip-activeno longer forces a pill radius. See the migration note. UiHeaderContentrenamedhideActionsOnMobiletohideActionsWhenNarrow, defaulting tofalse. The old name described a device, but the mechanism is a container query: a narrow header collapses inside a wide window too. The old default also destroyed the page's primary actions below 700px unless you opted out, so hiding them is now something you opt in to; otherwise they wrap below the title.UiHeaderContentemitsbackinstead ofclick. Declaringclickas an emit meant a consumer's@clicknever attached as a native listener — it fired only for the back button.@clicknow behaves like any other native listener, and@backcarries the intent.UiHeaderContent'scontainerClassacceptsClassValue. It previously tookstring | string[]and stringified the object form to[object Object]. Existing values keep working.UiPaneContent'scustomClassandcontainerClassacceptClassValue. Same widening asUiHeaderContent; existing values keep working.- The page header's back-navigation class is
.page-header-back. It was.page-header-breadcrumb, which described a component the header does not render. The back button is also centred against the title block now, instead of aligning to its top edge. - The header's bottom margin moved to
.header-containerand has its own token.--_header-block-endand--_header-back-gapsplit out of--_header-columns-gap, which was setting the title/actions gap and the space below the header at once. The margin sat on the inner.header, so a padded or tinted container trapped it inside its own box and amb-*utility on the container stacked on top of it. On the container it is a single margin that utilities override, sinceutilitiesis layered aftercomponents. UiHeaderContenttakes anasprop. The title was always anh1, so a page using the component for its section headers emitted several. It defaults toh1; passas="h2"and below for sections.UiHeaderContenttakes aheadlineprop. It renders a small eyebrow label above the title withcaption text-primary.UiHeaderContenttakes asizeprop.sm(the default,--fs-xl) through2xl(--fs-5xl), each step also applying the line height the type scale pairs with it.UiHeroContentuses the samesm–xlvocabulary on the display scale, so one size language covers both.- The header title no longer uses the
text-variablemixin, and its 992px breakpoint is gone. The mixin pinned the title towght/GRAD/wdthvariation axes that most families do not ship, and swapped the typeface behind an@supportstest;$type-h1already gives theh1the same family and weight in a way every font honours. The breakpoint then swapped--fs-xlfor--fs-lg, but the--fs-*scale is already fluid, so the title shrank twice and landed at 16px on a phone. It now scales once, 20px to 24px. UiHeaderContentandUiHeroContenttake aviewTransitionNameprop. The header hard-codedview-transition-name: selected-headeron every title andselected-descriptionon every description. A name has to be unique per document, so a page with more than one header — a page title plus its section headers — made the browser skip the transition entirely rather than run it. The name is also the key pairing two pages, which only the app knows, so it is now passed in and nothing is emitted without it.contain: paintand thefit-contentinline size come with it instead of applying to every header.- The footer's gradient underlay, hover radius and rounded top corners, and
.dialog-highlight. Both drew a gradient through a pseudo-element; the footer also squared its top corners on hover, and its corners are now flat with no radius transition left to animate. .footer-rowdeclares its own direction. It set a custom property the base rule never read, so a row group was horizontal only because that is the flex default. A row group with a title now keeps the heading on its own line instead of inline with the links.- Card surfaces are static and interaction is its own class.
.card-outline,.card-paneand.card-elevatednow only describe the surface;.card-selectableand.card-linkcarry every hover, press and focus state, including the elevated card'ssm→md→lgshadow escalation. A plain elevated card previously animated its shadow with nothing clickable behind it..card-linkalso picks up the same background feedback as a selectable card, which it had diverged from. See the migration note. - The selectable card keeps its variant's border and its own radius. It set
a 2px transparent border to reserve room for the selected ring; with
box-sizing: border-boxthe outer box never moved anyway, so the ring is a 1px border that matches a static.card-outlinebeside it. The selected and pressed states also hard-coded$rounded-lg/$rounded-xl, which ignored a configured$card-border-radius— on a 4px theme, clicking a card ran it 4px → 25px → 12px and left it there. The radius no longer changes. - The selectable card's ring answers
:focus-visible. It was:focus, so a mouse click left the ring on the card until it lost focus. - The selectable card's hover lift is 4px. It was 8px, which read as a
drag rather than a hover;
card-smsteps down to 2px alongside it.
Removed
UiHeaderContent'sactionsprop. Filling the#actionsslot is the signal; passing the slot without the flag used to render nothing, silently..subheading-m2. It set the same gutter.text-subheadingalready defaults to, so it never changed anything, and the reference documented it as a "large margin" it could not produce.subheading-m1andsubheading-m0stay for a tighter gutter or none.@colorffy/ui's./style.cssexport. The package no longer emits a stylesheet: its one scoped style block moved into@colorffy/css, which every consumer already loads. Nothing referenced the export, and it now pointed at a file that is not built..page-header-featuredand the header's.col-heading/.col-actionsrules. None of them had any markup behind them: the featured badge block styled a composition no component rendered, and the two media queries targeted classes that exist nowhere. The back-navigation and actions layouts are unaffected.
Fixed
subheading-m0/m1now move the space below the block. The modifiers set--_text-subheading-gutteron the heading, and a custom property never reaches a sibling, so the description below kept its default margin whichever modifier was applied. Each modifier now also sets the description's margin.- The title-to-description gap no longer follows the gutter. It was half the
gutter, so tightening the space below the block squeezed the title against its
own description — 2px at
subheading-m1. It is now its own--_text-subheading-gap, fixed at--space-8. .card-selectableno longer erases the variant's border..card-outline.card-selectableand.card-frosted.card-selectablematched at the same specificity, and.card-selectablecame last in the file, so its--_card-border-color: transparentwon and the card rendered with no outline at all until it was pressed or selected. It no longer declares the colour at rest, which the base.cardalready defaults to transparent.- An interactive card's transitions all run.
.card-selectableand.card-linkeach declared their owntransition, so on a card that was both — whichUiCardproduces wheneverselectablemeetsto/href— the later rule won and the lift and the ring never animated.box-shadowwas also missing from the selectable list, so an elevated card jumped straight to its hover shadow. One shared declaration now covers every interactive card.--_card-easing-inalso has its non-linear()fallback back: it was only defined inside the@supportsblock, and an undefined custom property in atransitionmakes the whole declaration invalid at computed-value time, so withoutlinear()support the card had no transition at all. - A
card-panecard gives hover feedback in light mode. The shared hover steps the background to--theme-surface-overlay, which resolves to the same white as the pane's own surface in a light theme — an interactive pane card changed nothing under the pointer. Pane now steps to--theme-surface-a30, which contrasts in both schemes, and carries its own press step so:activeis not outranked by it.
The page header title scales down again
.header stepped its title down to --fs-lg below 992px through
:where(.col-heading h1:is(.text-title)) — a selector that matched nothing, and
whose :where() wrapper would have given it zero specificity even if it had.
The size now flows through --_header-title-size, overridden inside the
.header rule itself, so it carries the same specificity as the value it
replaces.
The header's view transitions actually apply
view-transition-name, contain: paint and the fit-content inline size for
.text-title / .text-description lived in a Vue scoped style block that
compiled to @colorffy/ui's dist/ui.css — a file nothing imported, so the
declarations never reached the page. They now live here in _header.scss
alongside the rest of the header rules.
Connected groups no longer flatten their inner corners
The rounded end of .btn-group-connected came from a fixed
--_btn-connected-radius: 50px. On a 32px-tall button that is 50px + 50px down
one side against a 32px box, and the spec answer to radii that overflow a side
is to scale every corner of that element by the same factor — here
32 / 100. The first button's inner corners, asking for the 4px of
--_btn-radius, rendered at 1.3px and read as square next to their siblings:
before first: 50, 1.3, 1.3, 50 middle: 4, 4, 4, 4
after first: 16, 4, 4, 16 middle: 4, 4, 4, 4
The radius is now derived from the button instead of hard-coded:
.btn-group-connected .btn {
--_btn-connected-radius: calc(var(--_btn-height) / 2);
}
The two radii on a side then sum to exactly --_btn-height, which min-height
guarantees is never taller than the button, so the scale factor stays 1 and the
inner corners keep their own value. It also tracks the size variants — btn-sm,
btn-lg and btn-icon each get the pill that fits them.
--_btn-connected-radius yourself, move it onto the buttons
(.btn-group-connected .btn) — a declaration on the group is now shadowed by
the one above.Vertical connected groups follow the button radius
.btn-group-vertical.btn-group-connected had three separate mismatches: middle
buttons were pinned to $button-border-radius instead of --_btn-radius, so a
group of btn-sm got 8px corners where the horizontal variant gave 4px; the
first and last button squared their inner corners to 0, although the group is
separated by a gap; and the joined variant set border-radius: 0 as a shorthand
that outranked the end rules, so vertical joined groups lost their pill ends
entirely. All three now behave like the horizontal variant.
2.5.0
Changed
- Every spacing and font-size value moved onto
:rootdesign tokens, with the old names kept as deprecated aliases. See the migration guide. - Tabs draw their active state with an anchored indicator element, the popover menu renders as a native popover, and its default header no longer draws the avatar block. See new in 2.5.