Getting started with Vite
This page walks a brand-new Vite React project from create-vite to a themed, rendering component — first without Tailwind, then with it. The two paths differ only in how the CSS is wired up.
A Vite app is simpler than the Next.js setup in one structural way: there are no server components, so the "use client" boundary does not exist. Every component — flat exports and compound ones alike — imports and renders from anywhere.
Without Tailwind
1. Create the project
npm create vite@latest my-app -- --template react-ts
cd my-appAny package manager works — pnpm create vite and yarn create vite take the same template flag.
2. Install the library
npm install @forte-ui/reactReact 18 or 19 and the matching react-dom are the only required peer dependencies. Base UI comes with the package.
3. Import the stylesheets
Two, imported once at the entry point. In src/main.tsx:
import "@forte-ui/react/theme.css";
import "@forte-ui/react/styles/reset.css";
import "./index.css";theme.css carries the whole system — the tokens, the colour ramps, the motion system, the accessibility responses. Everything in it sits inside @layer forte.*, and a cascade layer loses to unlayered author CSS, so your own stylesheets override the library without !important regardless of load order.
reset.css is the second one, and it is opt-in twice over: importing it does nothing until forte-reset appears on an element. It sets box-sizing: border-box and suppresses the platform tap highlight — the grey box Safari and Chrome paint over whatever a touch lands on, which ignores border-radius and lingers after your finger lifts. The library already suppresses it on its own components; this extends the same treatment to the markup you write. Drop both lines if you would rather keep the platform default — see the tap highlight on touch for the trade-off.
The class is what switches it on. It goes on <html>, which on Vite lives in index.html:
<html lang="en" class="forte-reset">
4. Replace the scaffold CSS
Replace src/index.css entirely:
:root {
--forte-accent-seed: #6d43d4;
}
body {
margin: 0;
background: var(--forte-color-background);
color: var(--forte-color-foreground);
font-family: var(--forte-font-sans);
}The seed is the theme: all twelve accent steps, the brand-tinted neutrals, and a readable text colour for solid fills derive from it — in both light and dark mode, with no JavaScript and no build step.
5. Render a component
Replace src/App.tsx:
import { Button } from "@forte-ui/react";
export default function App() {
return (
<main style={{ display: "grid", placeItems: "center", minHeight: "100dvh" }}>
<Button>It works</Button>
</main>
);
}npm run devThat is the whole integration: one install, one CSS import, one variable. And since there is no server/client boundary, the compound components — Tabs.Root, Dialog.Trigger, Menu.Item — work here exactly like Button does.
6. Light and dark
Both palettes are already there — with no attribute set, the page follows the OS through prefers-color-scheme. To pin one, set data-theme on the root element statically in index.html:
<html lang="en" class="forte-reset" data-theme="dark">That is what create-forte-ui --scheme dark writes — and it then leaves the toggle and replay script below out, since a static attribute has nothing to switch and nothing to replay.
To let people choose instead, drive the same attribute with the library's own switch. ThemeToggle goes anywhere; parking it in a corner is what create-forte-ui scaffolds, so src/App.tsx from step 5 becomes:
import { Button, ThemeToggle } from "@forte-ui/react";
export default function App() {
return (
<main style={{ display: "grid", placeItems: "center", minHeight: "100dvh" }}>
<ThemeToggle
style={{ position: "fixed", top: "var(--forte-space-4)", right: "var(--forte-space-4)" }}
/>
<Button>It works</Button>
</main>
);
}On the Tailwind path below, that same placement is className="fixed top-4 right-4" — the offsets are the same two space tokens either way.
A click writes the attribute and persists the choice to localStorage("forte-theme"). To replay it on the next visit before first paint — a bundled component runs too late — add one inline script to index.html's <head> (create-forte-ui scaffolds it for you):
<script>
(function(){try{var t=localStorage.getItem("forte-theme");if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t)}catch(e){}})();
</script>
Every derived colour re-resolves; there is no second stylesheet to load. The same attribute, along with .forte-theme / data-forte-theme scoping for islands, is covered in Theming, and useTheme is the hook under the toggle when you want your own control.
With Tailwind
The package ships a bridge stylesheet for Tailwind v4 that re-points Tailwind's theme at the forte-ui tokens, so bg-primary is --forte-color-primary and every utility follows the seed, dark mode and theme scopes exactly like the components do.
1. Create the project and install
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install @forte-ui/react tailwindcss @tailwindcss/viteTailwind v4 on Vite runs as a Vite plugin — no PostCSS config, no tailwind.config.js.
2. Add the Vite plugin
vite.config.ts:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
});3. Wire the three stylesheets
Replace src/index.css entirely (the scaffold-reset warning above applies here too, App.css included):
@import "@forte-ui/react/tailwind.css";
@import "tailwindcss";
@import "@forte-ui/react/theme.css";
@import "@forte-ui/react/styles/reset.css";
:root {
--forte-accent-seed: #6d43d4;
}src/main.tsx keeps importing only ./index.css.
Two orderings in that file are load-bearing, and they are the same two as on the Next.js path: the bridge's first line pins the cascade-layer order to theme, base, forte, components, utilities, so it must come before tailwindcss (or p-4 on a Button loses to the button's own padding) — and before theme.css, which also declares the forte layer (or Preflight's button { background: transparent } beats every component by layer order).
4. Pin the layer order in index.html
One line in <head>, before anything that loads CSS:
<style>@layer theme, base, forte, components, utilities;</style>
On Next.js the bridge's own @layer statement survives the build and this step does not exist. Vite's CSS pipeline is not so gentle: Tailwind's compiler re-slots the statement around its own output, the minifier merges statement rules, and JS-imported component CSS lands in its own chunk — and once base first appears after forte, Preflight beats every component by layer order alone. The symptom is unmistakable: buttons render as bare text on a page whose colours are otherwise right. A <style> in the document itself loads ahead of every stylesheet, layer order is fixed at first appearance, and nothing the bundler emits later can unpin it.
5. Style with token utilities
Tailwind's stock scales are deleted and rebuilt from the tokens, not extended — bg-slate-800, p-13 and text-white do not compile. Every utility that does compile responds to the seed, dark mode and theme scopes:
import { Button, Card, ThemeToggle } from "@forte-ui/react";
export default function App() {
return (
<main className="grid min-h-dvh place-items-center bg-background text-foreground">
<ThemeToggle className="fixed top-4 right-4" />
<Card.Root variant="elevated" className="items-start gap-5">
<h1 className="text-5 font-semibold">forte-ui + Tailwind</h1>
<p className="text-2 text-foreground-muted">
Utilities and components, one theme.
</p>
<Button>It works</Button>
</Card.Root>
</main>
);
}Note where items-start gap-5 and the toggle's fixed top-4 right-4 land: on components. That is the layer order from step 3 doing its job — utilities beat a component's own layout without !important.
The Light and dark step applies verbatim on this path — the same toggle, and the same replay script in index.html.
The full utility mapping, targeting component state from arbitrary variants (data-[loading]:opacity-70), and how to opt a stock scale back in are on the Tailwind page — including the shipped cn merger for conditional class lists (install tailwind-merge if you use it).
By default Tailwind v4's dark: variant keys on prefers-color-scheme. You will rarely need it — token utilities resolve through light-dark() and follow the theme by themselves — but if your toggle sets data-theme, re-key the variant after the bridge import:
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));Loading a font
The library reads one token for its font: --forte-font-sans. Your body rule from step 4 reads it too. So the whole job is: get your font's name into that token.
Load the font however you normally would — @font-face, Fontsource, a <link> tag — then, in src/index.css:
:root {
--forte-font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
}Done. Your body rule reads --forte-font-sans, so the page text switches; the components read the same token, so tooltips, menus and dialogs switch with it. Don't set the font on body directly instead — some component parts declare font-family: var(--forte-font-sans) themselves and would stay on the system font. And keep the ui-sans-serif, system-ui, sans-serif tail: it is the fallback if the font fails to load.
Where next
- Theming — the seed envelope, the secondary seed, neutral tint, and scoped themes.
- Presets —
data-forte-radius,data-forte-density,data-forte-motion. - Styling components — parts, states, and per-component knobs.
- Theme Studio — pick a seed visually and copy the CSS out.