Skip to content

About

A Zero-runtime CSS React component library supporting multi-theme and dark mode based on design tokens.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

462 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🍍 Pine Design System

One React component library, three design concepts β€” switch the whole look with a single prop.

npm version npm downloads license

The same components switching between the basic, game, and crayon designs in light and dark mode

Documentation β€’ Storybook β€’ GitHub


Features

  • Three design concepts β€” basic, game (pixel art), and crayon (hand-drawn), switched with one design prop
  • Light & dark mode β€” Every design ships both modes, with optional OS sync
  • Token-driven β€” Components only read semantic tokens, so a design swap never touches your code
  • Accessible by default β€” Built on Base UI headless primitives for keyboard and screen reader support
  • Zero-runtime CSS β€” Styles compiled ahead of time with Vanilla Extract
  • Small by default β€” Only the basic design's CSS loads up front; game and crayon CSS load on first use
  • Server Component friendly β€” Client entry is marked "use client", and a React-free /tokens entry is safe in server-only code
  • TypeScript native β€” Typed props with JSDoc on every component

Installation

npm install pine-design-system
# or
pnpm add pine-design-system
# or
yarn add pine-design-system

Requires React 18 or 19.

Quick Start

Wrap your app with ThemeProvider and import the stylesheet once:

import { ThemeProvider, Button, Badge, TextField } from "pine-design-system";
import "pine-design-system/style.css";

function App() {
	return (
		<ThemeProvider defaultDesign="basic">
			<Button intent="primary">Get Started</Button>
			<Badge intent="success">New</Badge>
			<TextField label="Email" placeholder="you@example.com" />
		</ThemeProvider>
	);
}

Switching Designs

The core idea of Pine: change the design, keep the code. Every component reads the active design from ThemeProvider.

Design Look
basic Clean and modern, with soft shadows β€” for product and business apps
game Pixel-art frames, pressable arcade buttons, and a bitmap font
crayon Hand-drawn strokes and a handwritten font β€” for playful or learning apps
<ThemeProvider design="game" theme="dark">
	<YourApp />
</ThemeProvider>

Switch at runtime with controlled props:

import { useState } from "react";
import { ThemeProvider, type Design, type Theme } from "pine-design-system";

function App() {
	const [design, setDesign] = useState<Design>("basic");
	const [theme, setTheme] = useState<Theme>("light");

	return (
		<ThemeProvider design={design} onDesignChange={setDesign} theme={theme} onThemeChange={setTheme}>
			<YourApp />
		</ThemeProvider>
	);
}

Or from anywhere inside the provider with useThemeRequired():

import { useThemeRequired } from "pine-design-system";

function DesignToggle() {
	const { design, setDesign } = useThemeRequired();
	return <button onClick={() => setDesign(design === "game" ? "basic" : "game")}>Toggle</button>;
}

The game and crayon stylesheets (style-game.css, style-crayon.css) are loaded by ThemeProvider the first time you switch to them. While one loads, cssLoading from the theme context is true. They load through your bundler (Vite, webpack, Next.js), which also emits their fonts, so there are no font files to copy or serve yourself. To skip the lazy load, import one up front, e.g. import "pine-design-system/style-game.css".

ThemeProvider props

Prop Type Default Description
defaultDesign "basic" | "game" | "crayon" "basic" Initial design (uncontrolled)
design "basic" | "game" | "crayon" β€” Design (controlled), use with onDesignChange
defaultTheme "light" | "dark" β€” Initial color mode, used when syncWithSystem is false
theme "light" | "dark" β€” Color mode (controlled), use with onThemeChange
syncWithSystem boolean true Follow the OS color scheme until the theme is set manually
primaryColor string β€” Hex color that generates a custom primary palette (e.g. "#6366f1")

Render one ThemeProvider at the root. To show another design or mode in part of the page, nest a ThemeProvider inside it: the outermost provider puts the theme class on <html>, and nested ones only theme their own subtree. The applyGlobal prop is deprecated and will be removed in the next major version.

Components

Component Description
Badge Status labels and counts
Button Actions in solid, outline, ghost, and weak variants
Checkbox Selectable input with label
Dialog Modal dialog with title, description, and action buttons
Dropdown Select input with anchored popup (side, align, sideOffset)
ProgressBar Determinate and indeterminate progress with accessible value text
Switch On/off toggle
Tab Tabbed navigation with accessible panels
Text Typography primitive with size, weight, and intent
TextField Text input and textarea with validation states
Tooltip / TooltipProvider Hover and focus popups, with shared open delay for groups

Most components share the same vocabulary of props:

  • intent β€” primary | secondary | success | warning | danger | neutral
  • size β€” small | medium | large (plus xlarge on Button)
  • rounded β€” small | medium | large (large is a full pill; in game the steps notch the pixel frame's corners)
  • className β€” merged with the component's own classes

See the documentation for every prop and live examples.

Next.js & Server Components

The main pine-design-system entry is a client boundary ("use client"), so you can render its components from Server Components as usual.

For server-only code (a Server Component's function body, Route Handlers, generateMetadata), import design tokens from the React-free /tokens entry:

import { colors, radius, spacing } from "pine-design-system/tokens";

To avoid a light/dark flash before hydration, inline getThemeInitScript() in your root layout:

// app/layout.tsx
import { getThemeInitScript } from "pine-design-system/tokens";

export default function RootLayout({ children }: { children: React.ReactNode }) {
	return (
		<html lang="en" suppressHydrationWarning>
			<head>
				<script dangerouslySetInnerHTML={{ __html: getThemeInitScript({ design: "basic" }) }} />
			</head>
			<body>{children}</body>
		</html>
	);
}

For AI Coding Agents

The package ships llms.txt and llms-full.txt (also inside node_modules/pine-design-system/), so coding agents can read the full component API without leaving your project.

Tech Stack

Contributing

Contributions are welcome! To run the project locally:

git clone https://github.com/3o14/pine-design-system.git
cd pine-design-system
pnpm install
pnpm run storybook   # Storybook on http://localhost:6006
pnpm run test        # Vitest
  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/amazing-feature)
  3. Commit your changes (git commit -m 'feat(Button): add amazing feature')
  4. Add a release note with pnpm changeset
  5. Open a Pull Request

License

MIT

About

A Zero-runtime CSS React component library supporting multi-theme and dark mode based on design tokens.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages