Concepts

Core implementation concepts for theme and responsive behavior

Dark mode

Light and dark mode are fully supported, while your app controls how the value is applied and persisted.

To set the current view mode, apply a [data-theme] attribute on the html element:

<html data-theme="dark">
  <!-- ... -->
</html>

The attribute can also be nested inside content scopes:

<html data-theme="dark">
  <body>
    <div>Dark mode content (root scope)</div>
    <div data-theme="light">Light mode content (nested scope)</div>
  </body>
</html>

Tailwind

Tailwind is configured to use [data-theme] for dark mode behavior.

<div class="bg-white dark:bg-black">
  <!-- ... -->
</div>

JavaScript helpers

import { applyDocumentTheme, getDocumentTheme, useDocumentTheme } from "@plexui/ui/theme";

applyDocumentTheme("dark");

const currentTheme = getDocumentTheme(); // "light" | "dark"

function Sample() {
  const theme = useDocumentTheme(); // live value from html[data-theme]
  return null;
}

Persistent theme example

import { applyDocumentTheme } from "@plexui/ui/theme";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";

export type Theme = "light" | "dark" | "system";
type ThemeState = { theme: Theme };

const INITIAL_STATE: ThemeState = { theme: "system" };

const store = create(
  persist(() => INITIAL_STATE, {
    name: "plexui:user:theme",
    storage: createJSONStorage(() => localStorage),
  }),
);

store.subscribe((state) => applyDocumentTheme(resolveTheme(state.theme)));

window
  .matchMedia("(prefers-color-scheme: dark)")
  .addEventListener("change", () => applyDocumentTheme(resolveTheme(store.getState().theme)));

function getSystemTheme(): "light" | "dark" {
  return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}

function resolveTheme(theme?: Theme): "light" | "dark" {
  if (theme == null || theme === "system") return getSystemTheme();
  return theme;
}

export function setTheme(theme: Theme) {
  store.setState({ theme });
}

Responsive design

Write styles mobile-first: base styles apply to all sizes, then override upward from breakpoints.

Default breakpoints

NameSizeCommon deviceExample usage
xs380pxPortrait mobileVery small screen overrides
sm576pxLandscape mobile, phabletCollapsed mobile nav, single-column list/detail
md768pxTabletExpanded primary nav, fixed left sidebar
lg1024pxLaptopTight multi-column layouts
xl1280pxDesktopSpacious layouts
2xl1536pxWidescreenLargest breakpoints

Tailwind usage

<div className="flex flex-col lg:flex-row" />

React usage

import { useBreakpoint } from "@plexui/ui/hooks/useBreakpoint";

function SampleComponent() {
  const isMedium = useBreakpoint("md");
  return null;
}