© {new Date().getFullYear()} Thesys Inc. All Rights Reserved
diff --git a/docs/app/(home)/sections/LogoStrip/LogoStrip.tsx b/docs/app/(home)/sections/LogoStrip/LogoStrip.tsx
index a3e319c3b..ea84aab62 100644
--- a/docs/app/(home)/sections/LogoStrip/LogoStrip.tsx
+++ b/docs/app/(home)/sections/LogoStrip/LogoStrip.tsx
@@ -22,10 +22,10 @@ const HOME_LOGOS: Logo[] = [
const CLOUD_LOGOS: Logo[] = [
{ src: "/logos/entelligence.svg", alt: "Entelligence" },
- { src: "/logos/pointlabs.svg", alt: "Pointlabs" },
+ { src: "/logos/pointlabs.webp", alt: "Pointlabs" },
{ src: "/logos/wisdom.svg", alt: "Wisdom" },
{ src: "/logos/mili.svg", alt: "Mili" },
- { src: "/logos/ficell-labs.svg", alt: "Ficell Labs" },
+ { src: "/logos/ficell-labs.webp", alt: "Ficell Labs" },
];
/* The track holds SETS copies of the logo list and animates by -50%, so the
diff --git a/docs/app/(home)/sections/Navbar/Navbar.tsx b/docs/app/(home)/sections/Navbar/Navbar.tsx
index 4f93a0e1a..20c992804 100644
--- a/docs/app/(home)/sections/Navbar/Navbar.tsx
+++ b/docs/app/(home)/sections/Navbar/Navbar.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { SiteMarketingHeader } from "@/components/site-marketing-header";
export function Navbar() {
diff --git a/docs/app/blog/fumadocs.css b/docs/app/blog/fumadocs.css
new file mode 100644
index 000000000..81d999ee3
--- /dev/null
+++ b/docs/app/blog/fumadocs.css
@@ -0,0 +1,2 @@
+@reference "../global.css";
+@import "fumadocs-ui/css/preset.css";
diff --git a/docs/app/blog/layout.tsx b/docs/app/blog/layout.tsx
index 184c4659a..a461173ca 100644
--- a/docs/app/blog/layout.tsx
+++ b/docs/app/blog/layout.tsx
@@ -1,7 +1,8 @@
-import "../(home)/globals.css";
import { WebsiteThemeProvider } from "@/components/website-theme-provider";
import type { Metadata } from "next";
+import "../(home)/globals.css";
import { BlogNavbar } from "./components/BlogNavbar";
+import "./fumadocs.css";
export const metadata: Metadata = {
alternates: {
diff --git a/docs/app/chat/_components/agent-surfaces/cloud-agent-surface.tsx b/docs/app/chat/_components/agent-surfaces/cloud-agent-surface.tsx
index 16125deb7..58ec05d9b 100644
--- a/docs/app/chat/_components/agent-surfaces/cloud-agent-surface.tsx
+++ b/docs/app/chat/_components/agent-surfaces/cloud-agent-surface.tsx
@@ -4,7 +4,7 @@ import { createCloudChatLLM } from "@/lib/openui-cloud/chat-llm";
import { DEFAULT_MODEL } from "@/lib/openui-cloud/models";
import { CLOUD_USER_ID_HEADER, getOrCreateCloudUserId } from "@/lib/openui-cloud/user-id";
import { defineArtifactCategories } from "@openuidev/react-headless";
-import { AgentInterface } from "@openuidev/react-ui";
+import { AgentInterface } from "@openuidev/react-ui/AgentInterface";
import {
chatLibrary,
presentationArtifactRenderer,
diff --git a/docs/app/chat/_components/agent-surfaces/cloud-model-switcher.tsx b/docs/app/chat/_components/agent-surfaces/cloud-model-switcher.tsx
index 8014f7e27..c0c1d9586 100644
--- a/docs/app/chat/_components/agent-surfaces/cloud-model-switcher.tsx
+++ b/docs/app/chat/_components/agent-surfaces/cloud-model-switcher.tsx
@@ -8,7 +8,7 @@ import {
SelectItem,
SelectLabel,
SelectTrigger,
-} from "@openuidev/react-ui";
+} from "@openuidev/react-ui/Select";
import { useMemo } from "react";
import styles from "../../chat-page.module.css";
diff --git a/docs/app/chat/_components/agent-surfaces/oss-agent-surface.tsx b/docs/app/chat/_components/agent-surfaces/oss-agent-surface.tsx
index 0ad98166d..afabe2cf3 100644
--- a/docs/app/chat/_components/agent-surfaces/oss-agent-surface.tsx
+++ b/docs/app/chat/_components/agent-surfaces/oss-agent-surface.tsx
@@ -1,12 +1,8 @@
"use client";
import { isDemoCreditsErrorPayload } from "@/lib/demo-credits";
-import {
- AgentInterface,
- openAIAdapter,
- openAIMessageFormat,
- type ChatLLM,
-} from "@openuidev/react-ui";
+import { openAIAdapter, openAIMessageFormat, type ChatLLM } from "@openuidev/react-headless";
+import { AgentInterface } from "@openuidev/react-ui/AgentInterface";
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
import { useMemo } from "react";
diff --git a/docs/app/chat/_components/chat-page-client.tsx b/docs/app/chat/_components/chat-page-client.tsx
index 64d711e5a..9531b0ceb 100644
--- a/docs/app/chat/_components/chat-page-client.tsx
+++ b/docs/app/chat/_components/chat-page-client.tsx
@@ -5,10 +5,17 @@ import { OPENUI_CLOUD_UNAVAILABLE_MESSAGE } from "@/lib/openui-cloud/errors";
import dynamic from "next/dynamic";
import { Component, useCallback, useState, type ReactNode } from "react";
import styles from "../chat-page.module.css";
-import { OssAgentSurface } from "./agent-surfaces/oss-agent-surface";
import { ChatPageHeader } from "./chat-page-header";
import type { ChatMode } from "./chat-types";
+const OssAgentSurface = dynamic(
+ () => import("./agent-surfaces/oss-agent-surface").then((module) => module.OssAgentSurface),
+ {
+ ssr: false,
+ loading: () => ,
+ },
+);
+
const CloudAgentSurface = dynamic(
() => import("./agent-surfaces/cloud-agent-surface").then((module) => module.CloudAgentSurface),
{
diff --git a/docs/app/chat/layout.tsx b/docs/app/chat/layout.tsx
index 87e87c6a8..481d32697 100644
--- a/docs/app/chat/layout.tsx
+++ b/docs/app/chat/layout.tsx
@@ -1,4 +1,6 @@
import { WebsiteThemeProvider } from "@/components/website-theme-provider";
+import "@openuidev/react-ui/styles/index.css";
+import "@openuidev/thesys/styles.css";
import type { ReactNode } from "react";
export default function ChatLayout({ children }: { children: ReactNode }) {
diff --git a/docs/app/compare/_components/agent-surfaces/cloud-agent-surface.tsx b/docs/app/compare/_components/agent-surfaces/cloud-agent-surface.tsx
index fd65d8217..bb8f5f309 100644
--- a/docs/app/compare/_components/agent-surfaces/cloud-agent-surface.tsx
+++ b/docs/app/compare/_components/agent-surfaces/cloud-agent-surface.tsx
@@ -2,7 +2,7 @@
import { createCloudChatLLM } from "@/lib/openui-cloud/chat-llm";
import { CLOUD_USER_ID_HEADER, getOrCreateCloudUserId } from "@/lib/openui-cloud/user-id";
-import { AgentInterface } from "@openuidev/react-ui";
+import { AgentInterface } from "@openuidev/react-ui/AgentInterface";
import { artifactRenderers, chatLibrary, useOpenuiCloudStorage } from "@openuidev/thesys";
import { useMemo, useState } from "react";
import type { ComparisonControllerRegistry } from "../comparison-mode-controller";
diff --git a/docs/app/compare/_components/agent-surfaces/markdown-agent-surface.tsx b/docs/app/compare/_components/agent-surfaces/markdown-agent-surface.tsx
index 4ecb0e523..6b6b4e1e2 100644
--- a/docs/app/compare/_components/agent-surfaces/markdown-agent-surface.tsx
+++ b/docs/app/compare/_components/agent-surfaces/markdown-agent-surface.tsx
@@ -1,6 +1,6 @@
"use client";
-import { AgentInterface } from "@openuidev/react-ui";
+import { AgentInterface } from "@openuidev/react-ui/AgentInterface";
import type { ComparisonControllerRegistry } from "../comparison-mode-controller";
import { ComparisonModeControllerBridge } from "../comparison-mode-controller";
import { ComparisonSurfaceWelcome } from "./comparison-surface-welcome";
diff --git a/docs/app/compare/_components/agent-surfaces/oss-agent-surface.tsx b/docs/app/compare/_components/agent-surfaces/oss-agent-surface.tsx
index 20ef99a7b..1ef633cbc 100644
--- a/docs/app/compare/_components/agent-surfaces/oss-agent-surface.tsx
+++ b/docs/app/compare/_components/agent-surfaces/oss-agent-surface.tsx
@@ -1,6 +1,6 @@
"use client";
-import { AgentInterface } from "@openuidev/react-ui";
+import { AgentInterface } from "@openuidev/react-ui/AgentInterface";
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
import type { ComparisonControllerRegistry } from "../comparison-mode-controller";
import { ComparisonModeControllerBridge } from "../comparison-mode-controller";
diff --git a/docs/app/compare/_components/agent-surfaces/use-comparison-chat-llm.ts b/docs/app/compare/_components/agent-surfaces/use-comparison-chat-llm.ts
index 0272e39bb..c9b098175 100644
--- a/docs/app/compare/_components/agent-surfaces/use-comparison-chat-llm.ts
+++ b/docs/app/compare/_components/agent-surfaces/use-comparison-chat-llm.ts
@@ -1,7 +1,7 @@
"use client";
import { isDemoCreditsErrorPayload } from "@/lib/demo-credits";
-import { openAIAdapter, openAIMessageFormat, type ChatLLM } from "@openuidev/react-ui";
+import { openAIAdapter, openAIMessageFormat, type ChatLLM } from "@openuidev/react-headless";
import { useMemo } from "react";
type ComparisonResponseMode = "markdown" | "openui";
diff --git a/docs/app/compare/_components/compare-page-client.tsx b/docs/app/compare/_components/compare-page-client.tsx
index 5d6059246..e9540418c 100644
--- a/docs/app/compare/_components/compare-page-client.tsx
+++ b/docs/app/compare/_components/compare-page-client.tsx
@@ -14,7 +14,6 @@ import {
} from "react";
import styles from "../chat-page.module.css";
import { MarkdownAgentSurface } from "./agent-surfaces/markdown-agent-surface";
-import { OssAgentSurface } from "./agent-surfaces/oss-agent-surface";
import { ChatPageHeader } from "./chat-page-header";
import {
COMPARISON_MODE_LABELS,
@@ -36,6 +35,14 @@ import {
const ALL_MODES = ["markdown", "oss", "cloud"] as const;
+const OssAgentSurface = dynamic(
+ () => import("./agent-surfaces/oss-agent-surface").then((module) => module.OssAgentSurface),
+ {
+ ssr: false,
+ loading: () => ,
+ },
+);
+
const CloudAgentSurface = dynamic(
() => import("./agent-surfaces/cloud-agent-surface").then((module) => module.CloudAgentSurface),
{
diff --git a/docs/app/compare/layout.tsx b/docs/app/compare/layout.tsx
index 62d083a86..9e99f49ec 100644
--- a/docs/app/compare/layout.tsx
+++ b/docs/app/compare/layout.tsx
@@ -1,4 +1,6 @@
import { WebsiteThemeProvider } from "@/components/website-theme-provider";
+import "@openuidev/react-ui/styles/index.css";
+import "@openuidev/thesys/styles.css";
import type { ReactNode } from "react";
export default function CompareLayout({ children }: { children: ReactNode }) {
diff --git a/docs/app/components/blocks/accordion/page.tsx b/docs/app/components/blocks/accordion/page.tsx
index 262644945..b5fa8ccc9 100644
--- a/docs/app/components/blocks/accordion/page.tsx
+++ b/docs/app/components/blocks/accordion/page.tsx
@@ -6,12 +6,14 @@ import {
AccordionContent,
AccordionItem,
AccordionTrigger,
+} from "@openuidev/react-ui/Accordion";
+import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
-} from "@openuidev/react-ui";
+} from "@openuidev/react-ui/Select";
import { useState } from "react";
import styles from "./page.module.css";
diff --git a/docs/app/components/blocks/button-group/page.tsx b/docs/app/components/blocks/button-group/page.tsx
index bdac44a10..59016616d 100644
--- a/docs/app/components/blocks/button-group/page.tsx
+++ b/docs/app/components/blocks/button-group/page.tsx
@@ -6,16 +6,16 @@ import {
PreviewSection,
SegmentedToggle,
} from "@components/components/preview";
+import { Button } from "@openuidev/react-ui/Button";
+import { Buttons } from "@openuidev/react-ui/Buttons";
+import { IconButton } from "@openuidev/react-ui/IconButton";
import {
- Button,
- Buttons,
- IconButton,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
-} from "@openuidev/react-ui";
+} from "@openuidev/react-ui/Select";
import { useState } from "react";
import styles from "./page.module.css";
diff --git a/docs/app/components/blocks/callout/page.tsx b/docs/app/components/blocks/callout/page.tsx
index c4f8a1caf..bda20cc81 100644
--- a/docs/app/components/blocks/callout/page.tsx
+++ b/docs/app/components/blocks/callout/page.tsx
@@ -7,8 +7,8 @@ import {
SelectItem,
SelectTrigger,
SelectValue,
- TextCallout,
-} from "@openuidev/react-ui";
+} from "@openuidev/react-ui/Select";
+import { TextCallout } from "@openuidev/react-ui/TextCallout";
import { useState } from "react";
type CalloutVariant = "neutral" | "info" | "warning" | "success" | "danger";
diff --git a/docs/app/components/blocks/charts/area/page.tsx b/docs/app/components/blocks/charts/area/page.tsx
index 521cf57e4..047940cfb 100644
--- a/docs/app/components/blocks/charts/area/page.tsx
+++ b/docs/app/components/blocks/charts/area/page.tsx
@@ -2,7 +2,16 @@
import ClientOnly from "@components/blocks/_components/ClientOnly";
import { BlockVariantPreview, PreviewPage, PreviewSection } from "@components/components/preview";
-import { AreaChart, AreaChartCondensed } from "@openuidev/react-ui";
+import dynamic from "next/dynamic";
+
+const AreaChart = dynamic(
+ () => import("@openuidev/react-ui/Charts").then((module) => module.AreaChart),
+ { ssr: false },
+);
+const AreaChartCondensed = dynamic(
+ () => import("@openuidev/react-ui/Charts").then((module) => module.AreaChartCondensed),
+ { ssr: false },
+);
const areaData = [
{ month: "Jan", desktop: 150, mobile: 90 },
diff --git a/docs/app/components/blocks/charts/bar/page.tsx b/docs/app/components/blocks/charts/bar/page.tsx
index 811a83052..79f678405 100644
--- a/docs/app/components/blocks/charts/bar/page.tsx
+++ b/docs/app/components/blocks/charts/bar/page.tsx
@@ -2,7 +2,16 @@
import ClientOnly from "@components/blocks/_components/ClientOnly";
import { BlockVariantPreview, PreviewPage, PreviewSection } from "@components/components/preview";
-import { BarChart, BarChartCondensed } from "@openuidev/react-ui";
+import dynamic from "next/dynamic";
+
+const BarChart = dynamic(
+ () => import("@openuidev/react-ui/Charts").then((module) => module.BarChart),
+ { ssr: false },
+);
+const BarChartCondensed = dynamic(
+ () => import("@openuidev/react-ui/Charts").then((module) => module.BarChartCondensed),
+ { ssr: false },
+);
const barData = [
{ month: "Jan", desktop: 150, mobile: 90 },
diff --git a/docs/app/components/blocks/charts/donut/page.tsx b/docs/app/components/blocks/charts/donut/page.tsx
index 59af6b50c..5666ea08b 100644
--- a/docs/app/components/blocks/charts/donut/page.tsx
+++ b/docs/app/components/blocks/charts/donut/page.tsx
@@ -2,7 +2,12 @@
import BlocksDocPage from "@components/blocks/_components/BlocksDocPage";
import ClientOnly from "@components/blocks/_components/ClientOnly";
-import { PieChart } from "@openuidev/react-ui";
+import dynamic from "next/dynamic";
+
+const PieChart = dynamic(
+ () => import("@openuidev/react-ui/Charts").then((module) => module.PieChart),
+ { ssr: false },
+);
const donutData = [
{ category: "January", value: 1250 },
diff --git a/docs/app/components/blocks/charts/line/page.tsx b/docs/app/components/blocks/charts/line/page.tsx
index 3c56d8c08..4e4fe5a6a 100644
--- a/docs/app/components/blocks/charts/line/page.tsx
+++ b/docs/app/components/blocks/charts/line/page.tsx
@@ -2,7 +2,16 @@
import ClientOnly from "@components/blocks/_components/ClientOnly";
import { BlockVariantPreview, PreviewPage, PreviewSection } from "@components/components/preview";
-import { LineChart, LineChartCondensed } from "@openuidev/react-ui";
+import dynamic from "next/dynamic";
+
+const LineChart = dynamic(
+ () => import("@openuidev/react-ui/Charts").then((module) => module.LineChart),
+ { ssr: false },
+);
+const LineChartCondensed = dynamic(
+ () => import("@openuidev/react-ui/Charts").then((module) => module.LineChartCondensed),
+ { ssr: false },
+);
const lineData = [
{ month: "Jan", desktop: 150, mobile: 90 },
diff --git a/docs/app/components/blocks/charts/pie/page.tsx b/docs/app/components/blocks/charts/pie/page.tsx
index 6b511208b..e2372159d 100644
--- a/docs/app/components/blocks/charts/pie/page.tsx
+++ b/docs/app/components/blocks/charts/pie/page.tsx
@@ -2,7 +2,12 @@
import BlocksDocPage from "@components/blocks/_components/BlocksDocPage";
import ClientOnly from "@components/blocks/_components/ClientOnly";
-import { PieChart } from "@openuidev/react-ui";
+import dynamic from "next/dynamic";
+
+const PieChart = dynamic(
+ () => import("@openuidev/react-ui/Charts").then((module) => module.PieChart),
+ { ssr: false },
+);
const pieData = [
{ category: "January", value: 1250 },
diff --git a/docs/app/components/blocks/charts/radial/page.tsx b/docs/app/components/blocks/charts/radial/page.tsx
index 6bcf6989b..e32be7355 100644
--- a/docs/app/components/blocks/charts/radial/page.tsx
+++ b/docs/app/components/blocks/charts/radial/page.tsx
@@ -2,7 +2,12 @@
import BlocksDocPage from "@components/blocks/_components/BlocksDocPage";
import ClientOnly from "@components/blocks/_components/ClientOnly";
-import { RadialChart } from "@openuidev/react-ui";
+import dynamic from "next/dynamic";
+
+const RadialChart = dynamic(
+ () => import("@openuidev/react-ui/Charts").then((module) => module.RadialChart),
+ { ssr: false },
+);
const radialData = [
{ month: "January", value: 1250 },
diff --git a/docs/app/components/blocks/charts/scatter/page.tsx b/docs/app/components/blocks/charts/scatter/page.tsx
index d8abd6c5b..d30b081ba 100644
--- a/docs/app/components/blocks/charts/scatter/page.tsx
+++ b/docs/app/components/blocks/charts/scatter/page.tsx
@@ -2,7 +2,12 @@
import BlocksDocPage from "@components/blocks/_components/BlocksDocPage";
import ClientOnly from "@components/blocks/_components/ClientOnly";
-import { ScatterChart } from "@openuidev/react-ui";
+import dynamic from "next/dynamic";
+
+const ScatterChart = dynamic(
+ () => import("@openuidev/react-ui/Charts").then((module) => module.ScatterChart),
+ { ssr: false },
+);
const scatterData = [
{
diff --git a/docs/app/components/blocks/checkbox-group/page.tsx b/docs/app/components/blocks/checkbox-group/page.tsx
index ee9b71871..2ef4fad41 100644
--- a/docs/app/components/blocks/checkbox-group/page.tsx
+++ b/docs/app/components/blocks/checkbox-group/page.tsx
@@ -1,7 +1,8 @@
"use client";
import BlocksDocPage from "@components/blocks/_components/BlocksDocPage";
-import { CheckBoxGroup, CheckBoxItem } from "@openuidev/react-ui";
+import { CheckBoxGroup } from "@openuidev/react-ui/CheckBoxGroup";
+import { CheckBoxItem } from "@openuidev/react-ui/CheckBoxItem";
function CheckboxGroupPreview() {
return (
diff --git a/docs/app/components/blocks/date-picker/page.tsx b/docs/app/components/blocks/date-picker/page.tsx
index 61f749a56..79ce6f15f 100644
--- a/docs/app/components/blocks/date-picker/page.tsx
+++ b/docs/app/components/blocks/date-picker/page.tsx
@@ -1,7 +1,7 @@
"use client";
import BlocksDocPage from "@components/blocks/_components/BlocksDocPage";
-import { DatePicker } from "@openuidev/react-ui";
+import { DatePicker } from "@openuidev/react-ui/DatePicker";
function DatePickerPreview() {
return ;
diff --git a/docs/app/components/blocks/image-items/page.tsx b/docs/app/components/blocks/image-items/page.tsx
index 88f86f559..e1fb4b13e 100644
--- a/docs/app/components/blocks/image-items/page.tsx
+++ b/docs/app/components/blocks/image-items/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { BlockVariantPreview, PreviewPage, PreviewSection } from "@components/components/preview";
-import { ImageBlock } from "@openuidev/react-ui";
+import { ImageBlock } from "@openuidev/react-ui/ImageBlock";
import styles from "./page.module.css";
const DEFAULT_IMAGE =
diff --git a/docs/app/components/blocks/input-field/page.tsx b/docs/app/components/blocks/input-field/page.tsx
index 81c789160..26d927ee1 100644
--- a/docs/app/components/blocks/input-field/page.tsx
+++ b/docs/app/components/blocks/input-field/page.tsx
@@ -1,7 +1,7 @@
"use client";
import BlocksDocPage from "@components/blocks/_components/BlocksDocPage";
-import { Input } from "@openuidev/react-ui";
+import { Input } from "@openuidev/react-ui/Input";
function InputFieldPreview() {
return ;
diff --git a/docs/app/components/blocks/lists/page.tsx b/docs/app/components/blocks/lists/page.tsx
index 8e07c26ce..71d3a51f3 100644
--- a/docs/app/components/blocks/lists/page.tsx
+++ b/docs/app/components/blocks/lists/page.tsx
@@ -6,7 +6,8 @@ import {
PreviewSection,
SegmentedToggle,
} from "@components/components/preview";
-import { ListBlock, ListItem } from "@openuidev/react-ui";
+import { ListBlock } from "@openuidev/react-ui/ListBlock";
+import { ListItem } from "@openuidev/react-ui/ListItem";
import { ChevronRight, FileText, Globe, LayoutPanelTop } from "lucide-react";
import { useMemo, useState } from "react";
import styles from "./page.module.css";
diff --git a/docs/app/components/blocks/radio-button-group/page.tsx b/docs/app/components/blocks/radio-button-group/page.tsx
index 3b0010613..1a6047838 100644
--- a/docs/app/components/blocks/radio-button-group/page.tsx
+++ b/docs/app/components/blocks/radio-button-group/page.tsx
@@ -1,7 +1,8 @@
"use client";
import BlocksDocPage from "@components/blocks/_components/BlocksDocPage";
-import { RadioGroup, RadioItem } from "@openuidev/react-ui";
+import { RadioGroup } from "@openuidev/react-ui/RadioGroup";
+import { RadioItem } from "@openuidev/react-ui/RadioItem";
function RadioButtonGroupPreview() {
return (
diff --git a/docs/app/components/blocks/select/page.tsx b/docs/app/components/blocks/select/page.tsx
index cde712be4..5cdd47bec 100644
--- a/docs/app/components/blocks/select/page.tsx
+++ b/docs/app/components/blocks/select/page.tsx
@@ -10,7 +10,7 @@ import {
SelectSeparator,
SelectTrigger,
SelectValue,
-} from "@openuidev/react-ui";
+} from "@openuidev/react-ui/Select";
function SelectPreview() {
return (
diff --git a/docs/app/components/blocks/tables/page.tsx b/docs/app/components/blocks/tables/page.tsx
index 5f97aa359..d078e5075 100644
--- a/docs/app/components/blocks/tables/page.tsx
+++ b/docs/app/components/blocks/tables/page.tsx
@@ -8,8 +8,8 @@ import {
TableHead,
TableHeader,
TableRow,
- Tag,
-} from "@openuidev/react-ui";
+} from "@openuidev/react-ui/Table";
+import { Tag } from "@openuidev/react-ui/Tag";
function TablePreview() {
return (
diff --git a/docs/app/components/blocks/tabs/page.tsx b/docs/app/components/blocks/tabs/page.tsx
index d4f3fcf0f..575622286 100644
--- a/docs/app/components/blocks/tabs/page.tsx
+++ b/docs/app/components/blocks/tabs/page.tsx
@@ -6,7 +6,7 @@ import {
PreviewSection,
SegmentedToggle,
} from "@components/components/preview";
-import { Tabs, TabsList, TabsTrigger } from "@openuidev/react-ui";
+import { Tabs, TabsList, TabsTrigger } from "@openuidev/react-ui/Tabs";
import { useState } from "react";
import styles from "./page.module.css";
diff --git a/docs/app/components/blocks/tag-item/page.tsx b/docs/app/components/blocks/tag-item/page.tsx
index 0f20ed377..005c288cd 100644
--- a/docs/app/components/blocks/tag-item/page.tsx
+++ b/docs/app/components/blocks/tag-item/page.tsx
@@ -1,7 +1,7 @@
"use client";
import BlocksDocPage from "@components/blocks/_components/BlocksDocPage";
-import { Tag } from "@openuidev/react-ui";
+import { Tag } from "@openuidev/react-ui/Tag";
function TagItemPreview() {
const tags = [
diff --git a/docs/app/components/blocks/toggle-group/page.tsx b/docs/app/components/blocks/toggle-group/page.tsx
index 2c5de58b6..77606633c 100644
--- a/docs/app/components/blocks/toggle-group/page.tsx
+++ b/docs/app/components/blocks/toggle-group/page.tsx
@@ -1,7 +1,8 @@
"use client";
import BlocksDocPage from "@components/blocks/_components/BlocksDocPage";
-import { SwitchGroup, SwitchItem } from "@openuidev/react-ui";
+import { SwitchGroup } from "@openuidev/react-ui/SwitchGroup";
+import { SwitchItem } from "@openuidev/react-ui/SwitchItem";
function ToggleGroupPreview() {
return (
diff --git a/docs/app/components/components/ProAccessBanner/ProAccessBanner.tsx b/docs/app/components/components/ProAccessBanner/ProAccessBanner.tsx
index 6be64f5cf..919fbadef 100644
--- a/docs/app/components/components/ProAccessBanner/ProAccessBanner.tsx
+++ b/docs/app/components/components/ProAccessBanner/ProAccessBanner.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Button } from "@openuidev/react-ui";
+import { Button } from "@openuidev/react-ui/Button";
import styles from "./ProAccessBanner.module.css";
interface ProAccessBannerProps {
diff --git a/docs/app/components/components/ThemeBuilder/ChartsSection.tsx b/docs/app/components/components/ThemeBuilder/ChartsSection.tsx
index de7d2d738..da88598bd 100644
--- a/docs/app/components/components/ThemeBuilder/ChartsSection.tsx
+++ b/docs/app/components/components/ThemeBuilder/ChartsSection.tsx
@@ -7,7 +7,7 @@ import {
PieChart,
RadialChart,
ScatterChart,
-} from "@openuidev/react-ui";
+} from "@openuidev/react-ui/Charts";
import styles from "./realBlocksCanvas.module.css";
const MONTHLY_DATA = [
diff --git a/docs/app/components/components/ThemeBuilder/realBlocksCanvas.tsx b/docs/app/components/components/ThemeBuilder/realBlocksCanvas.tsx
index 1c1d60c51..af7ab12a1 100644
--- a/docs/app/components/components/ThemeBuilder/realBlocksCanvas.tsx
+++ b/docs/app/components/components/ThemeBuilder/realBlocksCanvas.tsx
@@ -5,35 +5,37 @@ import {
AccordionContent,
AccordionItem,
AccordionTrigger,
- Button,
- Buttons,
- CheckBoxGroup,
- CheckBoxItem,
- DatePicker,
- IconButton,
- ImageBlock,
- Input,
- RadioGroup,
- RadioItem,
+} from "@openuidev/react-ui/Accordion";
+import { Button } from "@openuidev/react-ui/Button";
+import { Buttons } from "@openuidev/react-ui/Buttons";
+import { CheckBoxGroup } from "@openuidev/react-ui/CheckBoxGroup";
+import { CheckBoxItem } from "@openuidev/react-ui/CheckBoxItem";
+import { DatePicker } from "@openuidev/react-ui/DatePicker";
+import { IconButton } from "@openuidev/react-ui/IconButton";
+import { ImageBlock } from "@openuidev/react-ui/ImageBlock";
+import { Input } from "@openuidev/react-ui/Input";
+import { RadioGroup } from "@openuidev/react-ui/RadioGroup";
+import { RadioItem } from "@openuidev/react-ui/RadioItem";
+import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
- SwitchGroup,
- SwitchItem,
+} from "@openuidev/react-ui/Select";
+import { SwitchGroup } from "@openuidev/react-ui/SwitchGroup";
+import { SwitchItem } from "@openuidev/react-ui/SwitchItem";
+import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
- Tabs,
- TabsList,
- TabsTrigger,
- Tag,
- TextCallout,
-} from "@openuidev/react-ui";
+} from "@openuidev/react-ui/Table";
+import { Tabs, TabsList, TabsTrigger } from "@openuidev/react-ui/Tabs";
+import { Tag } from "@openuidev/react-ui/Tag";
+import { TextCallout } from "@openuidev/react-ui/TextCallout";
import dynamic from "next/dynamic";
import { useState } from "react";
import styles from "./realBlocksCanvas.module.css";
diff --git a/docs/app/components/components/preview/PromptCopyButton.tsx b/docs/app/components/components/preview/PromptCopyButton.tsx
index 8317e2816..d2b140d00 100644
--- a/docs/app/components/components/preview/PromptCopyButton.tsx
+++ b/docs/app/components/components/preview/PromptCopyButton.tsx
@@ -1,6 +1,6 @@
"use client";
-import { IconButton } from "@openuidev/react-ui";
+import { IconButton } from "@openuidev/react-ui/IconButton";
import { Check, Copy } from "lucide-react";
import { useEffect, useState } from "react";
diff --git a/docs/app/components/theme-builder/page.tsx b/docs/app/components/theme-builder/page.tsx
index 13fb9b5aa..a141a590b 100644
--- a/docs/app/components/theme-builder/page.tsx
+++ b/docs/app/components/theme-builder/page.tsx
@@ -1,7 +1,13 @@
"use client";
import { useAppTheme } from "@components/components/AppThemeProvider/AppThemeProvider";
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@openuidev/react-ui";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@openuidev/react-ui/Select";
import {
ThemeProvider,
swatch,
diff --git a/docs/app/deferred-analytics.tsx b/docs/app/deferred-analytics.tsx
new file mode 100644
index 000000000..735f6d689
--- /dev/null
+++ b/docs/app/deferred-analytics.tsx
@@ -0,0 +1,64 @@
+"use client";
+
+import { useEffect } from "react";
+
+const GA_ID = "G-MZ0TZ82NM2";
+const FALLBACK_DELAY_MS = 30_000;
+
+type AnalyticsWindow = Window & {
+ dataLayer?: unknown[][];
+};
+
+export function DeferredAnalytics() {
+ useEffect(() => {
+ let loaded = false;
+ let idleCallbackId: number | undefined;
+
+ const load = () => {
+ if (loaded) return;
+ loaded = true;
+
+ const analyticsWindow = window as AnalyticsWindow;
+ analyticsWindow.dataLayer ??= [];
+ analyticsWindow.dataLayer.push(["js", new Date()]);
+ analyticsWindow.dataLayer.push(["config", GA_ID]);
+
+ const script = document.createElement("script");
+ script.async = true;
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}`;
+ document.head.appendChild(script);
+ };
+
+ const scheduleAfterNextPaint = () => {
+ window.requestAnimationFrame(() => {
+ if ("requestIdleCallback" in window) {
+ idleCallbackId = window.requestIdleCallback(load, { timeout: 2_000 });
+ } else {
+ setTimeout(load, 0);
+ }
+ });
+ };
+
+ const interactionEvents = ["pointerdown", "keydown", "touchstart"] as const;
+ interactionEvents.forEach((eventName) => {
+ window.addEventListener(eventName, scheduleAfterNextPaint, {
+ once: true,
+ passive: true,
+ });
+ });
+
+ const fallbackTimer = window.setTimeout(load, FALLBACK_DELAY_MS);
+
+ return () => {
+ window.clearTimeout(fallbackTimer);
+ if (idleCallbackId !== undefined && "cancelIdleCallback" in window) {
+ window.cancelIdleCallback(idleCallbackId);
+ }
+ interactionEvents.forEach((eventName) => {
+ window.removeEventListener(eventName, scheduleAfterNextPaint);
+ });
+ };
+ }, []);
+
+ return null;
+}
diff --git a/docs/app/demo/github/components/ConversationPanel/ConversationPanel.tsx b/docs/app/demo/github/components/ConversationPanel/ConversationPanel.tsx
index 63bb44b66..5090ecb2e 100644
--- a/docs/app/demo/github/components/ConversationPanel/ConversationPanel.tsx
+++ b/docs/app/demo/github/components/ConversationPanel/ConversationPanel.tsx
@@ -1,6 +1,6 @@
"use client";
-import { IconButton } from "@openuidev/react-ui";
+import { IconButton } from "@openuidev/react-ui/IconButton";
import { MarkDownRenderer } from "@openuidev/react-ui/MarkDownRenderer";
import { ChevronRight, MessageSquare, Send, Square } from "lucide-react";
import { useEffect, useRef, useState } from "react";
diff --git a/docs/app/demo/github/components/GitHubConnect/GitHubConnect.tsx b/docs/app/demo/github/components/GitHubConnect/GitHubConnect.tsx
index 4b4c20f14..caf94ca3c 100644
--- a/docs/app/demo/github/components/GitHubConnect/GitHubConnect.tsx
+++ b/docs/app/demo/github/components/GitHubConnect/GitHubConnect.tsx
@@ -1,7 +1,7 @@
"use client";
import { GitHubIcon } from "@/components/brand-logo";
-import { Button } from "@openuidev/react-ui";
+import { Button } from "@openuidev/react-ui/Button";
import {
Activity,
Check,
diff --git a/docs/app/demo/github/components/Modal/Modal.tsx b/docs/app/demo/github/components/Modal/Modal.tsx
index 47b5d784d..e1ceca0d8 100644
--- a/docs/app/demo/github/components/Modal/Modal.tsx
+++ b/docs/app/demo/github/components/Modal/Modal.tsx
@@ -1,4 +1,4 @@
-import { IconButton } from "@openuidev/react-ui";
+import { IconButton } from "@openuidev/react-ui/IconButton";
import { X } from "lucide-react";
import type { ReactNode } from "react";
import { useCallback, useEffect } from "react";
diff --git a/docs/app/demo/github/components/PreviewPanel/PreviewPanel.tsx b/docs/app/demo/github/components/PreviewPanel/PreviewPanel.tsx
index 78bfbf79c..7031afee2 100644
--- a/docs/app/demo/github/components/PreviewPanel/PreviewPanel.tsx
+++ b/docs/app/demo/github/components/PreviewPanel/PreviewPanel.tsx
@@ -1,6 +1,8 @@
import type { ActionEvent, ParseResult } from "@openuidev/react-lang";
import { Renderer } from "@openuidev/react-lang";
-import { IconButton, openuiLibrary, ThemeProvider } from "@openuidev/react-ui";
+import { openuiLibrary } from "@openuidev/react-ui/genui-lib";
+import { IconButton } from "@openuidev/react-ui/IconButton";
+import { ThemeProvider } from "@openuidev/react-ui/ThemeProvider";
import { Loader2, Maximize2, Monitor } from "lucide-react";
import { useCallback, useState } from "react";
import { Modal } from "../Modal/Modal";
diff --git a/docs/app/demo/github/layout.tsx b/docs/app/demo/github/layout.tsx
index 76adb44c1..6b4a79cf9 100644
--- a/docs/app/demo/github/layout.tsx
+++ b/docs/app/demo/github/layout.tsx
@@ -1,5 +1,6 @@
import { WebsiteThemeProvider } from "@/components/website-theme-provider";
import { createPageMetadata } from "@/lib/page-metadata";
+import "@openuidev/react-ui/styles/index.css";
import type { ReactNode } from "react";
import "./layout.css";
diff --git a/docs/app/demo/github/page.tsx b/docs/app/demo/github/page.tsx
index de85d4c22..a3180600f 100644
--- a/docs/app/demo/github/page.tsx
+++ b/docs/app/demo/github/page.tsx
@@ -3,7 +3,7 @@
import { DemoCreditsDialog } from "@/components/DemoCreditsDialog";
import { isDemoCreditsErrorPayload } from "@/lib/demo-credits";
import { mergeStatements } from "@openuidev/react-lang";
-import { Button } from "@openuidev/react-ui";
+import { Button } from "@openuidev/react-ui/Button";
import { encode } from "gpt-tokenizer";
import {
Activity,
@@ -18,12 +18,12 @@ import {
type LucideIcon,
} from "lucide-react";
import { useTheme } from "next-themes";
+import dynamic from "next/dynamic";
import { Highlight, themes } from "prism-react-renderer";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { ConversationPanel } from "./components/ConversationPanel/ConversationPanel";
import { GitHubConnect } from "./components/GitHubConnect/GitHubConnect";
import { Header } from "./components/Header/Header";
-import { PreviewPanel } from "./components/PreviewPanel/PreviewPanel";
import { SavedSidebar } from "./components/SavedSidebar/SavedSidebar";
import {
GITHUB_DEMO_MODEL_LABEL,
@@ -44,6 +44,11 @@ import {
type SavedDashboard,
} from "./saved/store";
+const PreviewPanel = dynamic(
+ () => import("./components/PreviewPanel/PreviewPanel").then((module) => module.PreviewPanel),
+ { ssr: false },
+);
+
// ── Helpers ─────────────────────────────────────────────────────────────────
function extractCodeOnly(response: string): string | null {
diff --git a/docs/app/demos/components/CatalogPanel/CatalogPanel.tsx b/docs/app/demos/components/CatalogPanel/CatalogPanel.tsx
index 0c8d392e2..f1a2e5da7 100644
--- a/docs/app/demos/components/CatalogPanel/CatalogPanel.tsx
+++ b/docs/app/demos/components/CatalogPanel/CatalogPanel.tsx
@@ -1,7 +1,7 @@
"use client";
import type { ComponentGroup } from "@openuidev/react-lang";
-import { openuiLibrary } from "@openuidev/react-ui";
+import { openuiLibrary } from "@openuidev/react-ui/genui-lib";
import { ChevronLeft, ChevronRight, LayoutList } from "lucide-react";
import { useState } from "react";
import "./CatalogPanel.css";
diff --git a/docs/app/demos/components/PreviewPanel/PreviewPanel.tsx b/docs/app/demos/components/PreviewPanel/PreviewPanel.tsx
index 022f95fca..9c92ac9d6 100644
--- a/docs/app/demos/components/PreviewPanel/PreviewPanel.tsx
+++ b/docs/app/demos/components/PreviewPanel/PreviewPanel.tsx
@@ -1,6 +1,7 @@
import type { ParseResult } from "@openuidev/react-lang";
import { Renderer } from "@openuidev/react-lang";
-import { openuiLibrary, ThemeProvider } from "@openuidev/react-ui";
+import { openuiLibrary } from "@openuidev/react-ui/genui-lib";
+import { ThemeProvider } from "@openuidev/react-ui/ThemeProvider";
import { Loader2, Maximize2, Monitor } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import type { Theme } from "../../constants";
diff --git a/docs/app/demos/layout.tsx b/docs/app/demos/layout.tsx
index d061b81dc..1b17411a3 100644
--- a/docs/app/demos/layout.tsx
+++ b/docs/app/demos/layout.tsx
@@ -1,5 +1,6 @@
import { WebsiteThemeProvider } from "@/components/website-theme-provider";
import { createPageMetadata } from "@/lib/page-metadata";
+import "@openuidev/react-ui/styles/index.css";
import type { ReactNode } from "react";
import "./layout.css";
diff --git a/docs/app/demos/page.tsx b/docs/app/demos/page.tsx
index d1c3cb970..91a17ea46 100644
--- a/docs/app/demos/page.tsx
+++ b/docs/app/demos/page.tsx
@@ -4,12 +4,17 @@ import { DemoCreditsDialog } from "@/components/DemoCreditsDialog";
import { isDemoCreditsErrorPayload } from "@/lib/demo-credits";
import { Send, Square } from "lucide-react";
import { useTheme } from "next-themes";
+import dynamic from "next/dynamic";
import { useCallback, useRef, useState } from "react";
import { CodePanel } from "./components/CodePanel/CodePanel";
import { Header } from "./components/Header/Header";
-import { PreviewPanel } from "./components/PreviewPanel/PreviewPanel";
import { MODELS, STARTER_PROMPTS, type Model, type Status, type Theme } from "./constants";
+const PreviewPanel = dynamic(
+ () => import("./components/PreviewPanel/PreviewPanel").then((module) => module.PreviewPanel),
+ { ssr: false },
+);
+
export default function DemosPage() {
const { theme, setTheme } = useTheme();
const [prompt, setPrompt] = useState("");
diff --git a/docs/app/docs/fumadocs.css b/docs/app/docs/fumadocs.css
new file mode 100644
index 000000000..81d999ee3
--- /dev/null
+++ b/docs/app/docs/fumadocs.css
@@ -0,0 +1,2 @@
+@reference "../global.css";
+@import "fumadocs-ui/css/preset.css";
diff --git a/docs/app/docs/layout.tsx b/docs/app/docs/layout.tsx
index b17771b80..45fe82380 100644
--- a/docs/app/docs/layout.tsx
+++ b/docs/app/docs/layout.tsx
@@ -1,11 +1,15 @@
import { DocsRouteLayout } from "@/components/docs-route-layout";
import { WebsiteThemeProvider } from "@/components/website-theme-provider";
import { source } from "@/lib/source";
+import { RootProvider } from "fumadocs-ui/provider/next";
+import "./fumadocs.css";
export default function Layout({ children }: LayoutProps<"/docs">) {
return (
-
- {children}
-
+
+
+ {children}
+
+
);
}
diff --git a/docs/app/global.css b/docs/app/global.css
index 109d40605..d6c464953 100644
--- a/docs/app/global.css
+++ b/docs/app/global.css
@@ -1,12 +1,40 @@
-@import url("https://fonts.googleapis.com/css2?family=Playwrite+US+Trad&family=Geist:wght@400;500;600;700&display=swap");
@import "tailwindcss";
@import "fumadocs-ui/css/neutral.css";
-@import "fumadocs-ui/css/preset.css";
@import "../shared/design-system/styles/variables/index.css";
-@import "@openuidev/react-ui/styles/index.css";
-@import "@openuidev/thesys/styles.css";
body {
+ /* Static aliases cover routes that only consume design tokens. Interactive
+ component-library routes can still override them with ThemeProvider. */
+ --openui-background: var(--background);
+ --openui-foreground: var(--foreground);
+ --openui-highlight-subtle: var(--highlight-subtle);
+ --openui-highlight: var(--highlight);
+ --openui-highlight-strong: var(--highlight-strong);
+ --openui-inverted-background: var(--inverted-background);
+ --openui-text-neutral-primary: var(--text-neutral-primary);
+ --openui-text-neutral-secondary: var(--text-neutral-secondary);
+ --openui-text-neutral-tertiary: var(--text-neutral-tertiary);
+ --openui-text-accent-primary: var(--text-accent-primary);
+ --openui-text-info-primary: var(--text-info-primary);
+ --openui-text-info-inverted: var(--text-info-inverted);
+ --openui-text-white: var(--text-white);
+ --openui-text-black: var(--text-black);
+ --openui-border-default: var(--border-default);
+ --openui-border-interactive: var(--border-interactive);
+ --openui-border-accent: var(--border-accent);
+ --openui-border-accent-emphasis: var(--border-accent-emphasis);
+ --openui-radius-s: var(--radius-s);
+ --openui-radius-4xl: var(--radius-4xl);
+ --openui-radius-full: var(--radius-full);
+ --openui-shadow-s: var(--shadow-s);
+ --openui-shadow-m: var(--shadow-m);
+ --openui-shadow-l: var(--shadow-l);
+ --openui-font-body: var(--font-body);
+ --openui-font-code: var(--font-code);
+ --openui-font-heading: var(--font-heading);
+ --openui-font-label: var(--font-label);
+ --openui-font-numbers: var(--font-numbers);
+
--color-fd-background: var(--openui-background);
--color-fd-foreground: var(--openui-text-neutral-primary);
--color-fd-muted: var(--openui-highlight-subtle);
diff --git a/docs/app/layout.tsx b/docs/app/layout.tsx
index 7f8afaff8..3db245dad 100644
--- a/docs/app/layout.tsx
+++ b/docs/app/layout.tsx
@@ -1,20 +1,15 @@
-import { RootProvider } from "fumadocs-ui/provider/next";
import type { Metadata } from "next";
-import { Geist_Mono, Inter } from "next/font/google";
-import Script from "next/script";
+import { Inter } from "next/font/google";
import { BASE_URL } from "../lib/source";
+import { DeferredAnalytics } from "./deferred-analytics";
import "./global.css";
import { PHProvider } from "./providers";
+import { SiteThemeProvider } from "./site-theme-provider";
const inter = Inter({
subsets: ["latin"],
});
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
-
const SITE_TITLE = "OpenUI - The Open Standard for Generative UI";
const SITE_DESCRIPTION =
"Full-stack, renderer-agnostic Generative UI with a streaming-first language, official React support, community integrations, and up to 67% fewer tokens than JSON.";
@@ -84,23 +79,12 @@ export const metadata: Metadata = {
export default function Layout({ children }: LayoutProps<"/">) {
return (
-
+
-
-
- {children}
-
-
-
-
+
+ {children}
+
+
);
diff --git a/docs/app/providers.tsx b/docs/app/providers.tsx
index 253ebec37..44c2a37d8 100644
--- a/docs/app/providers.tsx
+++ b/docs/app/providers.tsx
@@ -1,20 +1,41 @@
"use client";
+import { loadPostHog } from "@/lib/posthog-client";
import { addThesysLinkAttribution } from "@/lib/thesys-link-attribution";
-import posthog from "posthog-js";
-import { PostHogProvider } from "posthog-js/react";
import { useEffect } from "react";
+const FALLBACK_DELAY_MS = 30_000;
+
export function PHProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
- posthog.init("phc_3OLW53x09ZTVZSV6BEpj5uycj3ooqR6KOemOjx04e3D", {
- api_host: "https://dgoeivjus9jfp.cloudfront.net",
- capture_pageview: "history_change",
- disable_session_recording: false,
- session_recording: {
- sampleRate: 0.1,
- },
- });
+ let loaded = false;
+ let idleCallbackId: number | undefined;
+ let posthogDistinctId: string | undefined;
+ let posthogSessionId: string | undefined;
+
+ const load = () => {
+ if (loaded) return;
+ loaded = true;
+
+ void loadPostHog()
+ .then((posthog) => {
+ posthogDistinctId = posthog.get_distinct_id();
+ posthogSessionId = posthog.get_session_id();
+ })
+ .catch(() => {
+ // Analytics failures must not affect navigation.
+ });
+ };
+
+ const scheduleAfterNextPaint = () => {
+ window.requestAnimationFrame(() => {
+ if ("requestIdleCallback" in window) {
+ idleCallbackId = window.requestIdleCallback(load, { timeout: 2_000 });
+ } else {
+ setTimeout(load, 0);
+ }
+ });
+ };
const decorateLink = (anchor: HTMLAnchorElement) => {
const href = anchor.getAttribute("href");
@@ -23,39 +44,14 @@ export function PHProvider({ children }: { children: React.ReactNode }) {
const attributedHref = addThesysLinkAttribution(
href,
window.location.href,
- posthog.get_distinct_id(),
- posthog.get_session_id(),
+ posthogDistinctId,
+ posthogSessionId,
);
if (attributedHref !== href) anchor.setAttribute("href", attributedHref);
};
- const decorateLinksWithin = (root: ParentNode) => {
- if (root instanceof HTMLAnchorElement) decorateLink(root);
- root.querySelectorAll
("a[href]").forEach(decorateLink);
- };
-
- decorateLinksWithin(document);
-
- const observer = new MutationObserver((mutations) => {
- for (const mutation of mutations) {
- if (mutation.type === "attributes") {
- if (mutation.target instanceof HTMLAnchorElement) decorateLink(mutation.target);
- continue;
- }
-
- for (const node of mutation.addedNodes) {
- if (node instanceof Element) decorateLinksWithin(node);
- }
- }
- });
-
- observer.observe(document.body, {
- attributes: true,
- attributeFilter: ["href"],
- childList: true,
- subtree: true,
- });
+ document.querySelectorAll("a[href]").forEach(decorateLink);
const refreshLinkAtInteraction = (event: Event) => {
if (!(event.target instanceof Element)) return;
@@ -66,11 +62,27 @@ export function PHProvider({ children }: { children: React.ReactNode }) {
document.addEventListener("pointerdown", refreshLinkAtInteraction, true);
document.addEventListener("click", refreshLinkAtInteraction, true);
+ const interactionEvents = ["pointerdown", "keydown", "touchstart"] as const;
+ interactionEvents.forEach((eventName) => {
+ window.addEventListener(eventName, scheduleAfterNextPaint, {
+ once: true,
+ passive: true,
+ });
+ });
+
+ const fallbackTimer = window.setTimeout(load, FALLBACK_DELAY_MS);
+
return () => {
- observer.disconnect();
+ window.clearTimeout(fallbackTimer);
+ if (idleCallbackId !== undefined && "cancelIdleCallback" in window) {
+ window.cancelIdleCallback(idleCallbackId);
+ }
document.removeEventListener("pointerdown", refreshLinkAtInteraction, true);
document.removeEventListener("click", refreshLinkAtInteraction, true);
+ interactionEvents.forEach((eventName) => {
+ window.removeEventListener(eventName, scheduleAfterNextPaint);
+ });
};
}, []);
- return {children};
+ return children;
}
diff --git a/docs/app/site-theme-provider.tsx b/docs/app/site-theme-provider.tsx
new file mode 100644
index 000000000..57afe7b26
--- /dev/null
+++ b/docs/app/site-theme-provider.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import { ThemeProvider } from "next-themes";
+import type { ReactNode } from "react";
+
+export function SiteThemeProvider({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/docs/components/brand-logo.module.css b/docs/components/brand-logo.module.css
index 17a2f0f37..b24185178 100644
--- a/docs/components/brand-logo.module.css
+++ b/docs/components/brand-logo.module.css
@@ -1,3 +1,126 @@
+.thesysLink {
+ position: relative;
+ display: block;
+ flex-shrink: 0;
+ width: 1.5rem;
+ height: 1.5rem;
+ cursor: pointer;
+}
+
+.thesysMark {
+ position: absolute;
+ z-index: 1;
+ display: block;
+ width: 100%;
+ height: 100%;
+ transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
+}
+
+.thesysRect,
+.thesysPath,
+.thesysGlow {
+ transition:
+ fill 160ms ease,
+ opacity 160ms ease,
+ transform 160ms ease;
+}
+
+.thesysRect {
+ fill: rgb(0 0 0 / 4%);
+}
+
+.thesysPath {
+ fill: #000;
+}
+
+.thesysGlow {
+ position: absolute;
+ inset: -0.25rem;
+ z-index: 0;
+ border-radius: 0.5rem;
+ background: rgb(0 0 0 / 5%);
+ opacity: 0;
+ transform: scale(0.5);
+}
+
+.thesysLink:hover .thesysMark,
+.thesysLink:focus-visible .thesysMark {
+ transform: scale(1.15) rotate(8deg);
+}
+
+.thesysLink:hover .thesysRect,
+.thesysLink:focus-visible .thesysRect {
+ fill: #000;
+}
+
+.thesysLink:hover .thesysPath,
+.thesysLink:focus-visible .thesysPath {
+ fill: #fff;
+}
+
+.thesysLink:hover .thesysGlow,
+.thesysLink:focus-visible .thesysGlow {
+ opacity: 1;
+ transform: scale(1);
+}
+
+.thesysLinkDark .thesysRect {
+ fill: rgb(255 255 255 / 12%);
+}
+
+.thesysLinkDark .thesysPath {
+ fill: #fff;
+}
+
+.thesysLinkDark .thesysGlow {
+ background: rgb(255 255 255 / 10%);
+}
+
+.thesysLinkDark:hover .thesysRect,
+.thesysLinkDark:focus-visible .thesysRect {
+ fill: #fff;
+}
+
+.thesysLinkDark:hover .thesysPath,
+.thesysLinkDark:focus-visible .thesysPath {
+ fill: #000;
+}
+
+.openuiLink {
+ display: flex;
+ align-items: center;
+ gap: 0.125rem;
+ text-decoration: none;
+}
+
+.openuiMascot {
+ position: relative;
+ flex-shrink: 0;
+ width: 2rem;
+ height: 2rem;
+}
+
+.openuiMascotImage {
+ position: absolute;
+ inset: 0;
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+.openuiText {
+ color: #000;
+ font-family: "Inter", sans-serif;
+ font-size: 15px;
+ font-weight: 600;
+ line-height: 1.5rem;
+}
+
+.openuiTextDark {
+ color: #fff;
+}
+
.githubButton {
position: relative;
display: inline-flex;
diff --git a/docs/components/brand-logo.tsx b/docs/components/brand-logo.tsx
index 4176b0d23..79c652cc0 100644
--- a/docs/components/brand-logo.tsx
+++ b/docs/components/brand-logo.tsx
@@ -1,104 +1,46 @@
"use client";
import svgPaths from "@/imports/svg-urruvoh2be";
-import { AnimatePresence, motion } from "motion/react";
import Link from "next/link";
import { useEffect, useState } from "react";
import styles from "./brand-logo.module.css";
-const COUNT_UP_DURATION = 1500;
-
-const LOGO_SPRING = { type: "spring", stiffness: 400, damping: 15 } as const;
-const LOGO_COLOR_TRANSITION = { duration: 0.25 } as const;
-const GLOW_TRANSITION = { duration: 0.2 } as const;
-
export type LogoVariant = "light" | "dark";
function cx(...classNames: Array) {
return classNames.filter(Boolean).join(" ");
}
-export function ThesysLogo({
- isHovered,
- onHoverChange,
- variant = "light",
-}: {
- isHovered: boolean;
- onHoverChange: (hovered: boolean) => void;
- variant?: LogoVariant;
-}) {
+export function ThesysLogo({ variant = "light" }: { variant?: LogoVariant }) {
const isDark = variant === "dark";
- const rectIdleColor = isDark ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.04)";
- const rectHoveredColor = isDark ? "#ffffff" : "#000000";
- const pathIdleColor = isDark ? "#ffffff" : "#000000";
- const pathHoveredColor = isDark ? "#000000" : "#ffffff";
- const glowClass = isDark
- ? "absolute -inset-1 rounded-lg bg-white/10"
- : "absolute -inset-1 rounded-lg bg-black/5";
-
return (
onHoverChange(true)}
- onMouseLeave={() => onHoverChange(false)}
+ aria-label="Visit Thesys"
+ className={`${styles.thesysLink} ${isDark ? styles.thesysLinkDark : ""}`.trim()}
>
-
-
-
-
-
-
- {isHovered && (
-
- )}
-
+
+
);
}
export function OpenUILogo({ variant = "light" }: { variant?: LogoVariant }) {
const isDark = variant === "dark";
- const textClass = isDark
- ? "font-['Geist',sans-serif] font-semibold text-[15px] text-white leading-6"
- : "font-['Geist',sans-serif] font-semibold text-[15px] text-black leading-6";
+ const textClass = `${styles.openuiText} ${isDark ? styles.openuiTextDark : ""}`.trim();
return (
-
+
{/* Shiro mascot */}
-
+
{/* eslint-disable-next-line @next/next/no-img-element */}
-

+
OpenUI
@@ -144,33 +86,23 @@ function fetchGitHubStarCount(repo: string): Promise
{
return inflight;
}
-export function useGitHubStarCount(repo: string) {
+export function useGitHubStarCount(repo: string, enabled = true) {
const [count, setCount] = useState(null);
useEffect(() => {
+ if (!enabled) return;
+
let cancelled = false;
void fetchGitHubStarCount(repo).then((target) => {
if (cancelled || target === null) return;
- const startCount = Math.max(target - 50, 0);
- const startTime = performance.now();
-
- setCount(startCount);
-
- const tick = () => {
- if (cancelled) return;
- const progress = Math.min((performance.now() - startTime) / COUNT_UP_DURATION, 1);
- setCount(Math.round(startCount + (target - startCount) * progress));
- if (progress < 1) requestAnimationFrame(tick);
- };
-
- requestAnimationFrame(tick);
+ setCount(target);
});
return () => {
cancelled = true;
};
- }, [repo]);
+ }, [enabled, repo]);
return count;
}
diff --git a/docs/components/site-header.tsx b/docs/components/site-header.tsx
index ecd1fb2ac..5ca60fd38 100644
--- a/docs/components/site-header.tsx
+++ b/docs/components/site-header.tsx
@@ -1,6 +1,4 @@
-"use client";
-
-import { useState, type CSSProperties, type ReactNode } from "react";
+import type { CSSProperties, ReactNode } from "react";
import { OpenUILogo, ThesysLogo, type LogoVariant } from "./brand-logo";
import styles from "./site-header.module.css";
@@ -9,11 +7,9 @@ function joinClasses(...classNames: Array) {
}
export function SiteHeaderBrand({ variant = "light" }: { variant?: LogoVariant }) {
- const [isHovered, setIsHovered] = useState(false);
-
return (
-
+
diff --git a/docs/components/site-marketing-header.module.css b/docs/components/site-marketing-header.module.css
index 4a50436e0..7d95e71bd 100644
--- a/docs/components/site-marketing-header.module.css
+++ b/docs/components/site-marketing-header.module.css
@@ -52,6 +52,7 @@
cursor: pointer;
background: rgb(0 0 0 / 60%);
backdrop-filter: blur(12px);
+ animation: mobileBackdropIn 0.2s ease both;
}
.mobileTrayWrap {
@@ -61,6 +62,27 @@
right: 0;
z-index: 50;
pointer-events: none;
+ animation: mobileTrayIn 0.2s ease both;
+}
+
+@keyframes mobileBackdropIn {
+ from {
+ opacity: 0;
+ }
+}
+
+@keyframes mobileTrayIn {
+ from {
+ opacity: 0;
+ transform: translateY(-8px);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .mobileBackdrop,
+ .mobileTrayWrap {
+ animation: none;
+ }
}
.mobileTray {
diff --git a/docs/components/site-marketing-header.tsx b/docs/components/site-marketing-header.tsx
index ecd176aa2..15e6bf8f6 100644
--- a/docs/components/site-marketing-header.tsx
+++ b/docs/components/site-marketing-header.tsx
@@ -10,7 +10,6 @@ import {
} from "@/components/site-primary-nav";
import { ThemeToggle } from "@/components/theme-toggle";
import { ArrowRight } from "lucide-react";
-import { AnimatePresence, motion } from "motion/react";
import { useTheme } from "next-themes";
import Link from "next/link";
import { useCallback, useEffect, useState, type ReactNode } from "react";
@@ -116,21 +115,8 @@ function MobileMenu({ onClose }: { onClose: () => void }) {
return (
<>
-
-
+
+
@@ -156,6 +142,7 @@ function MobileMenu({ onClose }: { onClose: () => void }) {
void }) {
-
+
>
);
}
@@ -183,14 +170,11 @@ export function SiteMarketingHeader({
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(borderMode === "always");
const { resolvedTheme } = useTheme();
- const [mounted, setMounted] = useState(false);
- useEffect(() => setMounted(true), []);
const resolvedBrandVariant =
- brandVariant ?? (mounted && resolvedTheme === "dark" ? "dark" : "light");
+ brandVariant ?? (resolvedTheme === "dark" ? "dark" : "light");
useEffect(() => {
if (borderMode === "always") {
- setIsScrolled(true);
return;
}
@@ -235,6 +219,7 @@ export function SiteMarketingHeader({
@@ -258,9 +243,7 @@ export function SiteMarketingHeader({
}
);
}
diff --git a/docs/components/theme-toggle.tsx b/docs/components/theme-toggle.tsx
index e264cc7fa..0b69b1758 100644
--- a/docs/components/theme-toggle.tsx
+++ b/docs/components/theme-toggle.tsx
@@ -3,7 +3,6 @@
import { cn } from "@/lib/cn";
import { Moon, Sun } from "@phosphor-icons/react";
import { useTheme } from "next-themes";
-import { useEffect, useState } from "react";
import styles from "./theme-toggle.module.css";
type ThemeToggleProps = {
@@ -15,9 +14,7 @@ type ThemeToggleProps = {
export function ThemeToggle({ className, onToggle, title, ariaLabel }: ThemeToggleProps) {
const { setTheme, resolvedTheme } = useTheme();
- const [mounted, setMounted] = useState(false);
- useEffect(() => setMounted(true), []);
- const isDark = mounted && resolvedTheme === "dark";
+ const isDark = resolvedTheme === "dark";
const nextTheme = isDark ? "light" : "dark";
const defaultLabel = `Switch to ${nextTheme} mode`;
diff --git a/docs/content/docs/openui-lang/components/lang-example.tsx b/docs/content/docs/openui-lang/components/lang-example.tsx
index 7ab270a76..5c672b497 100644
--- a/docs/content/docs/openui-lang/components/lang-example.tsx
+++ b/docs/content/docs/openui-lang/components/lang-example.tsx
@@ -9,8 +9,12 @@ import {
TabsTrigger,
} from "@/components/overview-components";
import { genuiOutput } from "@/components/overview-components/genui";
-import { Renderer } from "@openuidev/react-lang";
-import { openuiLibrary } from "@openuidev/react-ui";
+import dynamic from "next/dynamic";
+
+const RenderedOpenUIExample = dynamic(
+ () => import("./rendered-openui-example").then((module) => module.RenderedOpenUIExample),
+ { ssr: false },
+);
const renderableOutput = `root = Stack([welcomeCard])
welcomeCard = Card([welcomeHeader, welcomeBody])
@@ -115,7 +119,7 @@ const completion = await client.chat.completions.create({