Proper UI

Frameworks

Which Proper UI layer to use in React, Next.js, Vue, Angular, Svelte, Astro, plain HTML and vanilla JS, and exactly which components exist outside React.

Proper UI is a React 19 component library first. The React layer (@properui/ui, installed with the CLI) is the full library: 126 component groups, every page example, and React Aria behaviour underneath. Everything else on this page is a smaller, curated layer for projects that are not React, built on the same design tokens so both look the same.

Support matrix

FrameworkLayer to useWhat you get
React 19, Next.js (App/Pages), Vite, Remix@properui/ui via properui add (TSX source)Everything: all component groups, page examples, marketing sections, React Aria keyboard and screen-reader behaviour
Vue 3, Nuxt@properui/elements + @properui/tokens (or the html snippets)The curated component set below as <pui-*> custom elements, same tokens and themes
Angular@properui/elements with CUSTOM_ELEMENTS_SCHEMA + @properui/tokensSame as Vue
Svelte, SvelteKit@properui/elements + @properui/tokensSame as Vue
Astrohtml snippets in .astro files, @properui/elements for interactive piecesSame curated set. An Astro site with the React integration can also use @properui/ui in React islands (init --platform react)
Plain HTML, vanilla JSThe prebuilt stylesheet and script from a CDN, plus the html snippetsThe curated set as plain markup on pui- classes, with small dependency-free behaviours

The four packages:

PackageWhat it is
@properui/uiThe React components. Copied into a project as source by properui add; React-only.
@properui/tokensThe token layer as plain CSS: theme.css for Tailwind v4 in any framework, tokens.css as bare CSS variables, the theme presets, and properui.css/properui.min.css (tokens plus component classes, prebuilt, for a page with no build step).
@properui/htmlComponent classes (.pui-btn, .pui-modal, ...) written against the semantic tokens, small vanilla-JS behaviours (dropdowns, tabs, modals, tooltips, toasts, theme switching), and copy-paste .html snippets.
@properui/elementsLight-DOM custom elements (<pui-button>, <pui-modal>, ...) that render the @properui/html markup and call its behaviours. No framework runtime, no shadow root, so forms and global styles work.

Install

The CLI detects the framework and picks the layer. On Vue, Nuxt, Angular, Svelte, SvelteKit, Astro, or a folder with no package.json, it puts the project on the html platform:

npx @properui/cli@latest init          # writes components.json with "platform": "html"
npx @properui/cli@latest add buttons   # installs the -html snippet entry: .html files, not TSX
npx @properui/cli@latest list --platform html

With a global stylesheet and Tailwind v4, init adds the token and component CSS to it:

@import "tailwindcss";
@import "@properui/tokens/theme.css";
@import "@properui/html/css";
@source "./components/**/*.html";

Without a build step, link the prebuilt files instead:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@properui/tokens/dist/properui.min.css" />
<script src="https://cdn.jsdelivr.net/npm/@properui/html/dist/properui-html.iife.js" data-auto-init defer></script>
<!-- optional: the custom elements -->
<script src="https://cdn.jsdelivr.net/npm/@properui/elements/dist/properui-elements.global.js" defer></script>

With a bundler: npm i @properui/tokens @properui/html @properui/elements, then register the elements once with import "@properui/elements/register";. add of a React-only entry on an html project fails with one line naming the HTML alternative when there is one; --platform react|html on init overrides detection.

Dark mode works the same way everywhere: a .dark-mode class on <html> repoints every token. In the html layer, setTheme("light" | "dark" | "system") from @properui/html toggles it and remembers the choice.

Components outside React

These exist in the html layer. add name is what to pass to properui add on an html project (it resolves to the -html entry); the custom element column is the @properui/elements tag where one exists (the package README is the authoritative list).

Componentadd nameHTML classesReact counterpartCustom element
Buttonbuttons.pui-btnbuttons<pui-button>
Badgebadges.pui-badgebadges<pui-badge>
Inputinput.pui-field, .pui-label, .pui-input, .pui-hintinput<pui-input>
Textareatextarea.pui-textareatextarea<pui-textarea>
Checkboxcheckbox.pui-checkboxcheckbox<pui-checkbox>
Radioradio-buttons.pui-radioradio-buttonsmarkup only
Toggletoggle.pui-toggletoggle<pui-toggle>
Select (native)select.pui-selectselect (NativeSelect)<pui-select>
Avataravatar.pui-avataravatar<pui-avatar>
Cardcard.pui-cardnone (layout pattern)markup only
Alert / calloutalerts.pui-alertalerts, callout<pui-alert>
Tabstabs.pui-tabs, .pui-tabtabs<pui-tabs>
Tooltiptooltip.pui-tooltip, [data-pui-tooltip]tooltip<pui-tooltip>
Dropdown menudropdown.pui-dropdown, .pui-menudropdown<pui-dropdown>
Modalmodals.pui-modal on <dialog>modals<pui-modal>
Accordionaccordion.pui-accordion on <details>nonemarkup + initAccordions
Breadcrumbsbreadcrumbs.pui-breadcrumbsbreadcrumbs<pui-breadcrumbs>
Paginationpagination.pui-paginationpagination<pui-pagination>
Tabletable.pui-tabletablemarkup only
Progress barprogress-indicators.pui-progressprogress-indicators<pui-progress>
Skeletonskeleton.pui-skeletonskeleton<pui-skeleton>
Toastnotifications.pui-toast, .pui-toasternotificationstoast({ title }) from JS
Empty stateempty-state.pui-emptyempty-statemarkup only

Accessibility in this layer comes from native HTML (<dialog>, <details>, real form controls) plus the role and aria-* attributes the behaviours set, not from React Aria. It covers the common keyboard paths (Escape, arrow keys in menus and tabs, focus return from dialogs), but it is not a line-for-line port of the React components' behaviour.

React-only

Everything not in the table above has no non-React equivalent today, and add refuses it on an html project. That includes:

  • Base: button-group, file-upload-trigger, form, hover-card, menubar, number-input, popover, slider, tag-input, tags, text-editor, toggle-chip, video-player.
  • Application: activity-feed, ai-elements, app-navigation, card-headers, carousel, charts, code-editor, code-snippet, color-picker, command-menu, confirm-dialog, content-divider, data-table, date-picker, description-list, file-upload, filter-bar, gantt, gradient-picker, image-picker, inline-cta, kanban, loading-indicator, messaging, metrics, page-headers, progress-steps, resizable, scroll-area, section-footers, section-headers, slideout-menus, sortable-list, stepper, timeline, tree-view.
  • Every marketing section and every page example (marketing, marketing-examples, app-examples), plus the foundations and shared-assets groups.

Where a close html component exists (data-table → table, confirm-dialog → modal, button-group → button), the CLI names it in its error message. There are no Vue, Angular or Svelte ports of the React components: the custom elements and the html snippets are the cross-framework surface.

Per-framework guides

Install, stylesheet, theme toggle, and a button and a modal in each framework's own syntax: Vue, Angular, Svelte, Astro, plain HTML and vanilla JS. For React: Next.js and Vite.

The CLI side of this (detection, components.json platform, how add resolves names) is in the CLI guide.