Installation

Quick install guide for Plex UI. Add the npm package, wire up the stylesheet, and configure your router in a Next.js, Vite, or Remix app

Prerequisites

Plex UI is built on top of React and Tailwind 4. To use Plex UI, you'll need to have both installed in your project, and configured with your build system.

  • React 19 (install guide). React 18 is not supported, because components use React 19 APIs such as use().
  • Tailwind 4 (install guide)

Install steps

1. Install the package

npm install @plexui/ui

2. Setup styles

Add the foundation styles and Tailwind layers to the top of your global stylesheet (e.g. main.css):

/* Pin the cascade-layer order so Plex UI's reset wins over Tailwind preflight
   for our form controls. Without this declaration the order depends on
   first occurrence, which in Tailwind v4 means preflight overrides Plex
   UI's button/input/textarea normalisations. */
@layer theme, base, components, utilities;

@import "tailwindcss";
@import "@plexui/ui/css";
/* Required for Tailwind to find class references in Plex UI components. */
@source "../node_modules/@plexui/ui";

/* The rest of your application CSS */

On Vite, use the official plugin

If your bundler is Vite, drop the manual @layer line, the css.transformer override, and the <style> failsafe. @plexui/ui/vite does all three for you:

// vite.config.ts
import { defineConfig } from "vite";
import plexui from "@plexui/ui/vite";

export default defineConfig({
  plugins: [plexui()],
});

The plugin pins css.transformer = 'postcss' and build.cssMinify = 'esbuild' (so Vite 8's Lightning CSS minifier doesn't strip the layer declaration), and injects <style>@layer theme, base, components, utilities;</style> into <head> as a runtime failsafe before any other stylesheet.

Your main.css then only needs the imports, with no @layer …; line and no other ceremony:

@import "tailwindcss";
@import "@plexui/ui/css";
@source "../node_modules/@plexui/ui";

Pass plexui({ injectLayerOrder: false }) if you've already added the equivalent <style> tag to your index.html by hand.

Vite + Lightning CSS gotcha

If you're on Vite (default in Vite 5+) Lightning CSS is the active CSS transformer, and it reorders cascade layers strictly by first occurrence. In Vite 8 the minifier silently drops the bare @layer theme, base, components, utilities; declaration entirely. Symptoms when either path hits:

  • <button> loses cursor: pointer
  • <input> / <textarea> regains the iOS-Safari default chrome
  • Pill controls render square because --radius-full falls through
  • Solid buttons render with invisible text (preflight color: inherit wins over component CSS)

Three things together cover both Vite 5-7 and Vite 8:

  1. Keep the explicit @layer line at the top of main.css: it's enough on its own in Vite 5-7.

  2. Pin the CSS transformer + minifier so Lightning CSS doesn't strip the layer declaration on Vite 8:

    // vite.config.ts
    export default defineConfig({
      css: { transformer: 'postcss' },
      build: { cssMinify: 'esbuild' },
    });
  3. HTML failsafe: inject the layer order through <head> so it survives any minification path:

    <!-- index.html -->
    <head>
      <style>@layer theme, base, components, utilities;</style>
      <!-- the rest of your <head>, link/script tags etc. -->
    </head>

    Place it before any <link rel="stylesheet"> or bundler-injected stylesheet. The browser sees the layer order before any rule arrives, and your CSS imports just slot in.

Skipping Tailwind preflight (optional)

Plex UI ships its own opinionated reset in @plexui/ui/css, so Tailwind preflight is redundant. If you want only Tailwind utilities and theme, drop preflight:

@layer theme, base, components, utilities;
@import "tailwindcss/theme.css";
@import "tailwindcss/utilities.css";
@import "@plexui/ui/css";
@source "../node_modules/@plexui/ui";

This sidesteps every preflight-vs-Plex-UI ordering problem.

What the default import changes in your Tailwind theme

@plexui/ui/css replaces five Tailwind theme scales with Plex's, so the utilities match the components:

  • Text sizes: text-3xs to text-9xl use the Plex type scale (text-base is Plex's 16px text-md size).
  • Letter spacing: only tracking-tight, tracking-normal and tracking-wide.
  • Shadows: only shadow-sm, shadow-md, shadow-lg, shadow-xl and shadow-hairline (plus inset-shadow-hairline). shadow-2xl and the drop-shadow-* utilities are not generated.
  • Colors: only the Plex palettes (gray, red, orange, yellow, green, blue, purple, pink), white, black and alpha (used with an opacity, e.g. bg-alpha/15). Other Tailwind palettes such as slate or emerald are not generated.
  • Easing: only ease-basic and the ease-cubic-* curves. ease-in, ease-out and ease-in-out are not generated.

Radius, font and font-weight tokens (--radius-*, --font-sans, --font-mono, --font-weight-*) are declared on :root outside any cascade layer. Tailwind's utilities read the same variables, so where a name exists in both, Plex's value wins: rounded-sm is 6px, rounded-md 8px, rounded-lg 10px, and font-sans uses the Plex font stack. Plex-only names such as rounded-2xs are not generated, and breakpoints keep Tailwind's defaults (sm: at 640px, no xs:).

Using the full Plex scale in your Tailwind utilities (opt-in)

To make Tailwind's radius, font and breakpoint scales exactly Plex's (adds rounded-2xs, makes rounded-full use --radius-full, and moves the breakpoints to Plex's xs 380px and sm 576px), opt in with one extra import:

@layer theme, base, components, utilities;
@import "tailwindcss";
@import "@plexui/ui/css";
/* Optional: overrides Tailwind's default theme tokens with Plex's scale */
@import "@plexui/ui/styles/variables-tailwind-theme.css";
@source "../node_modules/@plexui/ui";

This file uses @theme static and resets Tailwind's --radius-*, --font-*, --font-weight-*, and --breakpoint-* so they take Plex's values instead of Tailwind's defaults. It also defines the --breakpoint-* variables that useBreakpoint reads. Use only if you actively want that. It's a project-wide decision, not a per-page one.

Then import your stylesheet before rendering any components:

// Must be imported first to ensure Tailwind layers and style foundations are defined before any potential component styles
import "./main.css"

import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import { App } from "./App"

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

3. Configure router (optional)

<PlexUIProvider> helps define your default router link component, used in components like <TextLink> and <ButtonLink>.

This provider is optional - router links can also be passed directly to components via the as prop.

// Must be imported first to ensure Tailwind layers and style foundations are defined before component styles
import "./main.css"

import { PlexUIProvider } from "@plexui/ui/components/PlexUIProvider"
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import { Link } from 'react-router'
import { App } from "./App"

declare global {
  interface PlexUIConfig {
    LinkComponent: typeof Link
  }
}

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <PlexUIProvider linkComponent={Link}>
      <App />
    </PlexUIProvider>
  </StrictMode>,
)

Start building

Your project is now ready to use Plex UI!

Here's an example of a simple reservation card, using Tailwind classes and components.

import { Badge } from "@plexui/ui/components/Badge"
import { Button } from "@plexui/ui/components/Button"
import {
  Calendar,
  Invoice,
  Maps,
  Members,
  Phone,
} from "@plexui/ui/components/Icon"

export function ReservationCard() {
  return (
    <div className="w-full max-w-sm rounded-2xl border border-default bg-surface shadow-lg p-4">
      <div className="flex items-start justify-between gap-3">
        <div>
          <p className="text-secondary text-sm">
            Reservation
          </p>
          <h2 className="mt-1 heading-lg">La Luna Bistro</h2>
        </div>
        <Badge color="success">Confirmed</Badge>
      </div>
      <div>
        <dl className="mt-4 grid grid-cols-[auto_1fr] gap-x-3 gap-y-2 text-sm">
          <dt className="flex items-center gap-1.5 font-medium text-secondary">
            <Calendar className="size-4" />
            Date
          </dt>
          <dd className="text-right">Apr 12 · 7:30 PM</dd>
          <dt className="flex items-center gap-1.5 font-medium text-secondary">
            <Members className="size-4" />
            Guests
          </dt>
          <dd className="text-right">Party of 2</dd>
          <dt className="flex items-center gap-1.5 font-medium text-secondary">
            <Invoice className="size-4" />
            Reference
          </dt>
          <dd className="text-right uppercase">4F9Q2K</dd>
        </dl>
      </div>
      <div className="mt-4 grid gap-3 border-t border-subtle pt-4 sm:grid-cols-2">
        <Button variant="soft" color="secondary" block>
          <Phone />
          Call
        </Button>
        <Button color="primary" block>
          <Maps />
          Directions
        </Button>
      </div>
    </div>
  )
}

Use the styles in any framework

The CSS behind every component ships as its own package, @plexui/styles: design tokens, resets, globals and one CSS module per component. @plexui/ui installs it for you, so a React app needs nothing extra. To use the same tokens and styles in a project without React, install it on its own. It has no React dependency, but it still needs Tailwind 4, because the stylesheet uses Tailwind directives.

npm install @plexui/styles
@layer theme, base, components, utilities;

@import "tailwindcss";
@import "@plexui/styles/css";

Component styles are CSS modules at @plexui/styles/components/<Dir>/<File>.module.css, for example @plexui/styles/components/Button/Button.module.css. Import them from your own components and let your bundler scope the class names, the same way @plexui/ui does. On Vite, @plexui/styles/vite is the same plugin as @plexui/ui/vite.