Skip to content

v0.20.0

Latest

Choose a tag to compare

@github-actions github-actions released this 26 Oct 20:22

Added

  • @plexui/vue: Plex UI for Vue 3. Every component, icon and meaningful hook has a Vue counterpart with the same names, props, defaults and rendered markup, styled by the same @plexui/styles CSS modules and checked against the React components by parity tests. Each component page has a Vue tab, and the Vue guide covers installation, the Vite plugin and SSR. The MCP server takes framework: "vue", llms-vue.txt describes the Vue API, and plexui.com/r/vue is a registry for the shadcn-vue CLI.
  • InputGroup: an input or textarea with icons, text, buttons and menus attached inside one frame. InputGroup.Addon sits before or after the control (align="inline-start" / "inline-end") or as a header or footer row ("block-start" / "block-end"); InputGroup.Button is a ghost Button sized from the group; InputGroup.Text holds prefixes, units and counters. Every Input size from 3xs to 3xl, both variants, pill, disabled and invalid. Also in @plexui/vue as InputGroup, InputGroupInput, InputGroupTextarea, InputGroupAddon, InputGroupButton and InputGroupText.
  • Menu.Item takes a description: a second line under the label, with the label going semibold on its own. Same "item + subtitle" anatomy a Select option with a description already had, so a two-line menu item no longer needs hand-written typography.
  • Select accepts checkPosition="none", which removes the indicator column from the list. A menu of one-shot actions, or a list whose option view states the selection itself, no longer reserves an empty 16px column in every row.
  • Slider accepts aria-label and aria-labelledby, so a slider without a visible label can be named. Range thumbs keep their Minimum and Maximum names.
  • MarkdownEditor accepts aria-label and aria-labelledby for its editable region, which a <label for> cannot name.
  • Menu.ItemAction accepts aria-label, so its icon-only button can be named.
  • DatePicker and DateRangePicker calendars move focus from the keyboard: the arrow keys by a day or a week, Home and End to the start or end of the week, Page Up and Page Down by a month. The calendar turns to the next or previous month when needed, and focus stays put rather than move to a day outside min and max.
  • Kbd: a key or a keyboard shortcut such as ⌘K, in text, in a tooltip or at the end of a menu item. Kbd.Group holds keys pressed together; three sizes, soft and outline, both mixing the surrounding text color so a key reads on any surface. Also in @plexui/vue as Kbd and KbdGroup.
  • Breadcrumb: the trail of pages above a page title. A nav landmark around an ordered list; Breadcrumb.Item is a link (href or to, through the linkComponent of PlexUIProvider), a button (onClick), a plain step or the current page (aria-current="page"). Custom separator, size="sm", and long trails wrap. Also in @plexui/vue as Breadcrumb and BreadcrumbItem (the separator is a slot).
  • Tabs take overflow="menu": the tabs that do not fit move into a menu at the end of the row instead of a sideways scroll. When the active tab is a hidden one, the menu button shows its name and carries the selection; badges move into the menu with their tabs. overflowLabel sets the button's text.
  • Dialog.Body (Vue DialogBody): with it, only the body scrolls and the header and footer stay in view. Dialog.Content takes placement (center, top, bottom, start, end, fullscreen) for search dialogs, bottom sheets and side panels, mobilePlacement for 640px and below (a centered dialog that becomes a bottom sheet on phones), and onOpenAutoFocus / onCloseAutoFocus.
  • Field takes a labelAction: a link or small control at the end of the label row, such as "Forgot password?".
  • Checkbox and RadioGroup.Item take a description: a second line under the label, outside it, linked through aria-describedby. Switch already had one.
  • Menu.Item and Menu.Link take danger for destructive actions: the label and the highlight use the danger color.
  • EmptyMessage takes size="sm" for an empty panel, card section or list, and align="start" for an empty state under a section heading.
  • Alert takes banner: a full-width strip with square corners and lines above and below only, for offline, read-only or new-version notices.
  • Input takes variant="ghost": no border and no fill, for a field its container already frames, such as a search line at the top of a dialog.
  • FieldError takes icon={false} for a message that stands alone, such as under a centered code input.
  • CopyButton and CopyTooltip report the clipboard result through onCopy(copied) (Vue: the copy event).
  • --sidebar-menu-sub-max-height sets the height an open SidebarMenuSub animates to (1000px by default), so long lists are not cut off.
  • Select takes searchSize for the search field in its menu, such as 2xl for 44px touch targets on phones.
  • @plexui/ui/styles/tailwind-utilities.css is exported, for apps that import the token files one by one instead of @plexui/ui/css.

Changed

  • @plexui/mcp 0.2.0: every tool takes framework: "vue" for the @plexui/vue docs, and get_component finds components by their exported and part names.
  • The CSS now ships in its own package, @plexui/styles: design tokens, globals and the CSS module of every component. @plexui/ui depends on it and re-exports it, so @import "@plexui/ui/css", the @plexui/ui/styles/*.css files, @plexui/ui/vite and @plexui/ui/types keep working unchanged. Rendered markup and compiled CSS are identical to 0.19. Install @plexui/styles on its own to use the same styles from another framework.
  • Grouped Select menus separate their sections. A group heading is now smaller and semibold in the secondary color instead of an option-sized muted row, a rule runs between sections, and the heading starts at the row's content edge instead of being indented to the option labels.
  • SidebarGroup no longer accepts onOpenChange. The group has no toggle of its own and never called it; set open from your own control, and a closed group hides its SidebarGroupContent.
  • @plexui/ui requires React 19 in peerDependencies. The components already used React 19 APIs (use(Context), <Context value>, ref as a prop) and could not run on React 18.
  • Published modules use fully specified imports (./Button.js, lodash/clamp.js), so the package resolves under webpack 5's strict ESM rules, Node ESM and TypeScript NodeNext. The PlexUI global types now ship with the declarations instead of being dropped from them.
  • Field takes its label size from the child control: <Field><Input size="sm" /></Field> gets an sm label without repeating the size. An explicit size on Field still wins, and a render prop keeps md.
  • FloatingLabelSelect shows its error as the same plain line as FloatingLabelInput, so the two read alike on one form. It used to render FieldError with an icon.
  • The Alert icon sits on the first line of the message, next to the title, instead of in the middle of a title and a description. A one-line alert looks the same.

Fixed

  • checkPosition="none" also drops the indicator column from a group's expand row (optionsLimit.label). The row kept a 16px slot and sat indented under options that no longer had one.
  • Select menus are never narrower than the field they belong to. listWidth="auto" still grows with the content, but the menu's min-width now falls back to the trigger's width instead of collapsing to nothing.
  • Select menus line up with their trigger's edge. align="start"/"end" no longer apply a hidden -5px offset that pushed the menu outside the field.
  • Select search inputs have a default accessible name and support searchAriaLabel independently of the trigger label. DatePicker's year search is named Search year.
  • DatePicker and DateRangePicker use a complete attribute selector when excluding exiting calendars from their height calculation.
  • Grouped Select lists expose each group to assistive technology: a role="group" named by its heading. The arrow keys still move through the options of every group in order, and the look is unchanged.
  • DatePicker month and year menus use Plex UI Select, with accessible names and consistent keyboard navigation and styling across browsers.
  • Select and DatePicker now forward accessible labels, descriptions and invalid state to their interactive triggers. aria-labelledby includes the visible selected value or placeholder, keeping both the field name and current selection available to assistive technology.
  • Select checkmarks are vertically centered on the title's first line at either position, including options with multiline descriptions.
  • CopyButton without children, the copy action in every CodeBlock, has an accessible name: Copy, then Copied after a click.
  • Slider thumbs take their accessible name from label (range thumbs keep Minimum and Maximum, as in "Price range Minimum"). In range mode the label no longer points at a missing input, and the reset button is labelled with resetTooltip.
  • TagInput keeps every tag when several are pasted at once (pasting a,b,c used to keep only c), stops at maxTags, and no longer throws when a tag containing a double quote is selected. Select option lookup escapes values the same way.
  • DatePicker and DateRangePicker calendars keep the min and max days selectable. A max at midnight or a min later in the day used to disable that whole day.
  • DatePicker and DateRangePicker month navigation buttons are labelled Previous month and Next month. The DateRangePicker month stepper also works with Enter and Space.
  • SidebarProvider with onOpenChange but no open expands and collapses again. Controlled mode follows the open prop; onOpenChange is always called.
  • Image renders a new src after an earlier one failed to load.
  • Switch no longer points aria-describedby at a description it does not render (a description without a label).
  • A Checkbox or Switch with a description inside a Field with one: the two descriptions have their own ids (the control's ends in -label-description) and the control is described by both. They shared an id, and a Switch dropped its own description.
  • DataTable supports uncontrolled row selection, filtering and column visibility, also with only an on*Change callback (row.getIsSelected() threw without a rowSelection prop). It re-paginates when pageSize changes, including to and from 0 or Infinity, and labels its pagination buttons and rows-per-page select.
  • Space and Enter on the clear button of SelectControl and FloatingLabelSelect clear the value instead of opening the list, also inside a Menu.Trigger.
  • CopyTooltip copies with Enter or Space, not only with a pointer press.
  • TagInput with maxTags={0} (no limit, as the input already treated it) no longer renders a stray 0 where the counter goes.
  • Input and Textarea forward autoComplete and set aria-invalid when invalid is true. FileUpload puts aria-invalid and its error description on the visible trigger instead of the hidden file input.
  • TextLink keeps an explicit target, as ButtonLink already did.
  • A TextLink without href but with onClick is focusable and runs its action on Enter and Space.
  • Button color and DatePicker triggerDateFormat are optional in the types, matching their runtime defaults.
  • Select returns focus to its trigger after choosing an option or pressing Escape; it used to drop focus to the page.
  • With showTime, DatePicker keeps the chosen date and time within min and max, and the time fields show the adjusted time.
  • ProgressSteps with variant="minimal" renders a div with role="progressbar" instead of a nav, where that role is not allowed.
  • The header of the primary sorted DataTable column carries aria-sort.
  • StatCard's info tooltip can be reached with the keyboard and is announced as "More information".
  • TagInput keeps a tab stop when maxTags is reached, so tags can still be removed from the keyboard.
  • MarkdownEditor toolbar toggles expose their state through aria-pressed.
  • FloatingLabelInput with multiline forwards props such as required, maxLength and key handlers to its textarea, and resizes when a controlled value or rows changes.
  • Field names controls that its label's for cannot reach. The label has an id, and a child without its own aria-label, aria-labelledby or label gets aria-labelledby pointing at it, so a Select or DatePicker trigger in a Field reads "Role, Admin" instead of only its value. A render prop receives aria-labelledby among the field props.
  • CopyButton (and so every CodeBlock copy button) and CopyTooltip show the copied state only after the clipboard write succeeds; a failed write used to show the check mark anyway. A second click while the write is in flight no longer copies twice.
  • A long FieldError message wraps inside the field, and its icon stays on the first line.
  • Sidebar: the collapsed header no longer hides or shrinks the collapse trigger; collapsed-state rules no longer hide labels, group labels and badges in an expanded sidebar nested inside a collapsed one; in React the phone layout is used on the first render instead of one frame of the desktop sidebar.
  • Toasts stay inside the screen on phones instead of keeping the 400px desktop width, with the same inset on both sides.
  • The opaque soft fills (--color-background-{info,success,warning,caution,danger,discovery}-soft and their hover and active states) have dark-theme values: they stayed light, under text that turned light.
  • Checkbox plays its check animation only for the checkbox in use, not for every checked box when a list first renders or a tab with checkboxes opens.
  • OTPInput cells narrow to fit a 320px screen, and a field cleared from outside (a wrong code wiped by the form) puts the caret back in the first cell when the focus was in the field.
  • Select's hidden form input uses its own class instead of Tailwind utilities, so it can no longer take the trigger's width in an app whose Tailwind build orders w-full after sr-only or does not generate sr-only.
  • The Tabs thumb follows the active tab when tabs are added, removed, reordered or relabelled.
  • Menu.Trigger forwards its props and ref to the trigger element.
  • AnimateLayoutGroup applies insertMethod and preventInitialTransition.
  • ButtonLink, TextLink and Menu.Link type their props from the link component set in PlexUIConfig, as the installation guide describes. The augmentation used to reach only PlexUIProvider, so <ButtonLink to="/x"> with a router Link failed to type-check. PlexUIOverrides still works.
  • SidebarMenuButton with asChild renders the child element as the button. The button props used to land on an inner span wrapped around the child, so a link lost the button styles and aria-current.
  • Sidebar no longer imports the undeclared @radix-ui/react-slot, which strict installs (pnpm without hoisting, Yarn PnP) could not resolve.
  • Field merges its aria-describedby with the child's own and keeps the child's explicit aria-invalid.
  • An Avatar with onClick uses name as its accessible name instead of announcing only the initial, or nothing for icon avatars.
  • A sidebar item's chevron turns only when that item is expanded, not when an item it is nested under is.
  • Slider, the useBreakpoint hook, useDocumentTheme and the userAgent helpers render on the server. useBreakpoint returns false on the server and on the first client render, then reads the viewport; useDocumentTheme returns light there, then follows the document.
  • Slider marks follow new values and labels when their count stays the same (changing min, max and marks together no longer throws), range marks are positioned, thumbs show a focus ring from the keyboard, the value input accepts negative numbers, and a typed value that has not been reported yet no longer overwrites a newer change. usePrevious returns the value before the latest change.
  • Select's option list is a listbox: aria-selected follows the selection, aria-activedescendant the highlighted option, and multiple selection sets aria-multiselectable. Typeahead in grouped options picks the next match, Ctrl, Cmd and Alt shortcuts no longer change the value, a multiple Select with name submits every value, and a disabled Select is neither submitted nor validated.
  • FloatingLabelSelect used as a Select or Menu trigger opens from the keyboard and gets focus back when the list closes.
  • Clicking the frame, an adornment or the label of an email or number Input or FloatingLabelInput no longer throws. Clear buttons keep focus in the field, and FloatingLabelInput keeps an explicit aria-invalid such as the one Field passes.
  • OTPInput keeps every digit in its cell, DateInput shows its value again when it loses focus with a partly typed date, a disabled TagInput cannot remove or add tags, and InputGroup keeps its invalid border while focused.
  • DatePicker and DateRangePicker accept equal min and max, name every day with its full date, mark the selection with aria-pressed and today with aria-current, and their id is optional. DateRangePicker disables shortcuts outside min, max or maxRangeDays, and starts a fresh selection after its value is reset to null.
  • DataTable enableRowSelection renders the checkbox column it promised, rows with onRowClick work from the keyboard, grouped headers span their columns, the rows-per-page select shows the real page size, and pagination stays while rows overflow it.
  • MarkdownEditor places the caret where you click, saves links as links instead of escaped HTML, runs Cancel and Remove on Enter, and keeps focus inside its link dialog. The CodeBlock copy button shows its focus ring, and expanded code taller than maxExpandedHeight scrolls.
  • FileUpload without multiple replaces its file instead of adding a second one.
  • Clicking the dimmed area around a Dialog closes it. A Menu.Item with only onClick is interactive, a disabled Menu.Link does not navigate, and Menu.Label has its 12px text again.
  • Image and Avatar show an image that loaded before hydration. Button and Badge keep a Tooltip trigger or a render-prop child intact.
  • A collapsed SidebarMenuSub leaves the tab order, and an onClick on SidebarTrigger or SidebarMobileMenuButton runs before the toggle and can cancel it.
  • ButtonGroup.Text has its padding and gap again, Button gutterSize="3xs" has a style, transition groups render children keyed like constructor, and AnimateLayoutGroup applies hideOverflow and itemAnchor.
  • @plexui/ui and @plexui/vue declarations pass a consumer type check with skipLibCheck off, also together (one ColumnMeta augmentation). @types/luxon and @tanstack/table-core are dependencies of the packages that use them.
  • The registry button and menu items no longer re-export types that do not exist. The registry styles item is imported from your stylesheet after Tailwind and carries @source, so Tailwind generates the classes the components use.
  • The MCP server finds components by their exported and part names (DataTable, Toaster, Dialog.Trigger), its React setup guide includes the layer order, @source and linkComponent, and llms-full.txt is generated from the docs.
  • @plexui/vue: Vue renders what React renders with radix-ui 1.8 (Tabs radiogroup, Tooltip content, aria-controls only while open, Popover and Dialog dismiss on the click after an outside press, RadioGroup, Checkbox and Switch form inputs) and follows every fix above. Vue-only fixes: Card, ButtonLink and TextLink give vue-router's RouterLink a to; DialogTrigger, DialogClose, MenuTrigger and Field work with a child forwarded through <slot />; closed Popover, Dialog and Select render no teleport on the server; Escape closes a Dialog with a forced-open Tooltip on the page; Input and FloatingLabelInput accept readonly; keyed multi-root <template v-for> items in transition groups; TagInput emits plain objects; DateRangePicker follows in-place edits of its range; missing props types are exported.

Full changelog: https://plexui.com/docs/changelog

Also published

  • @plexui/mcp@0.2.0