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.

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 — no @layer …; line, 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.

Using the Plex radius/font scale in your Tailwind utilities (opt-in)

By default Plex UI declares its tokens in :root, so the components render correctly without touching your Tailwind theme. Your rounded-md, rounded-full, text-sm utilities keep using Tailwind's own defaults.

If you want rounded-md to render with the Plex radius (0.5rem), rounded-full to render 9999px, and your text to default to the Plex font stack, 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. 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>
  )
}