{"id":24201349,"url":"https://github.com/slingshot/pte","last_synced_at":"2026-01-03T17:42:52.584Z","repository":{"id":166781059,"uuid":"642305771","full_name":"slingshot/pte","owner":"slingshot","description":"Universal theming utilities for any JavaScript or TypeScript environment","archived":false,"fork":false,"pushed_at":"2024-09-07T20:28:03.000Z","size":464,"stargazers_count":1,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-12T12:16:19.420Z","etag":null,"topics":["javascript","react","server-components","ssr","svelte","theming","typescript","vue"],"latest_commit_sha":null,"homepage":"https://npmjs.com/pte","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/slingshot.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-18T09:18:19.000Z","updated_at":"2024-09-07T20:27:14.000Z","dependencies_parsed_at":"2024-10-06T12:36:58.619Z","dependency_job_id":"20f83323-d7fa-4c76-9d7c-19d9f38f608c","html_url":"https://github.com/slingshot/pte","commit_stats":null,"previous_names":["slingshot/pte"],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slingshot%2Fpte","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slingshot%2Fpte/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slingshot%2Fpte/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slingshot%2Fpte/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/slingshot","download_url":"https://codeload.github.com/slingshot/pte/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233808778,"owners_count":18733601,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","react","server-components","ssr","svelte","theming","typescript","vue"],"created_at":"2025-01-13T21:15:40.746Z","updated_at":"2025-09-22T00:31:31.068Z","avatar_url":"https://github.com/slingshot.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pte\n\nThe Paris Theming Engine (`pte`) is a set of utilities that allow you to build a theming system for any JavaScript\napplications using only CSS custom properties.\n\nWe built `pte` to power theming in [Paris](https://github.com/slingshot/paris), our new React design system, because we\ncouldn't find an existing way to build theming that was (1) dynamic, (2) type-safe, and (3) compatible with Server\nComponents.\n\n## Highlights\n\n✅ Works with any JavaScript framework (React, Vue, Svelte, etc.)  \n✅ Works with any styling system or library (CSS Modules, Emotion, Tailwind, etc.)  \n✅ Highly-performant (theme changes don't cause re-renders because it's all CSS)  \n✅ Supports Server Components and SSR  \n✅ Supports dynamic theming, even for server components  \n✅ Allows type-safe theme updates\n\n## Installation\n\n```bash\npnpm install pte\n# or\nyarn add pte\n# or\nnpm install pte\n```\n\n## Usage\n\n### 1. Create a theme\n\nThe first step is to create a theme. A theme is a set of variables that define the look and feel of your application.\n\nUsing `pte`, you can create a theme using the `createTheme` function, which returns typed helpers for accessing the theme:\n\n```ts\n// pte.ts\nimport { createTheme } from 'pte';\n\nexport const {\n    theme,\n    pvar,\n    pget,\n    updateTheme,\n} = createTheme({\n    themeName: 'my-theme',\n    colors: {\n        primary: '#000',\n        secondary: '#fff',\n    },\n});\n```\n\n### 2. Inject the theme into your app\n\nIf your theme is initially static (i.e. you'll start with the same default values each time and adjust them after the page loads), the best way to load the theme's variables statically through a `\u003cstyle\u003e` tag. We export a `generateCSS` function that outputs a string of CSS variables that you can inject into your app. **You must ensure the style tag has the id `pte-vars` for dynamic theming to work.**\n\nHere's an example for Next.js with the `app` directory, where you can inject the theme into the `\u003chead\u003e` of your app from the server:\n\n```tsx\n// app/layout.tsx\nimport { generateCSS } from 'pte';\nimport { theme } from '../pte-init';\n// ...\nexport default function RootLayout({ children }) {\n    return (\n        \u003chtml lang=\"en\"\u003e\n        \u003chead\u003e\n            \u003cstyle\n                // This is required for dynamic theming to work properly\n                id=\"pte-vars\"\n                dangerouslySetInnerHTML={{\n                    __html: generateCSS(theme),\n                }}\n            /\u003e\n        \u003c/head\u003e\n        \u003cbody\u003e{children}\u003c/body\u003e\n        \u003c/html\u003e\n    );\n};\n```\n\nYou can use the built-in CLI to export static CSS files for your theme, which is especially helpful for getting IntelliSense to recognize your theme variables (but can also be used for static theme generation):\n\n```bash\n# Run `pte export --help` for more info\n# This will read a named export called `MyTheme` from `./src/my-theme.ts` and output a file called `pte.css` in the `./public` directory\npte export ./src/my-theme.ts MyTheme -o ./src/styles/pte.css\n```\n\u003cbr/\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eAlternative 1:\u003c/strong\u003e Static injection script\u003c/summary\u003e\n\nYou can also create a `\u003cscript\u003e` component that injects\nthe theme into the DOM _before_ your app is rendered. The `generateThemeInjection` function outputs a plain-text\nJavaScript function that can be injected into a script component, which in turn handles setting up the theme variables\nin your application.\n\nHere's an example for Next.js with the `app` directory:\n\n```tsx\n// app/layout.tsx\nimport { generateThemeInjection } from 'pte';\nimport { theme } from '../pte-init';\n// ...\nexport default function RootLayout({ children }) {\n    return (\n        \u003chtml lang=\"en\"\u003e\n        \u003chead\u003e\n            \u003cscript\n                id=\"set-pte-vars\"\n                type=\"text/javascript\"\n                // eslint-disable-next-line react/no-danger\n                dangerouslySetInnerHTML={{\n                    __html: generateThemeInjection(theme),\n                }}\n            /\u003e\n        \u003c/head\u003e\n        \u003cbody\u003e{children}\u003c/body\u003e\n        \u003c/html\u003e\n    );\n};\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eAlternative 2:\u003c/strong\u003e Client-side injection\u003c/summary\u003e\n\nAlternatively, you can invoke the `injectTheme` function directly on the client. This is useful if you're using a framework that doesn't utilize server-side rendering, or if you need to dynamically change the initial theme based on something on the client.\n\nIn any situation with any kind of server-side build step, we recommend using one of the above methods instead as they're more performant and don't require the client to wait for the theme to load before rendering. You can perform theme updates right after the client loads.\n\nFor example, in a Storybook preview container:\n\n```js\n// client.js\nimport { injectTheme } from 'pte';\nimport { theme } from '../pte-init';\n\nconst preview: Preview = {\n    docs: {\n        container: (props) =\u003e {\n            injectTheme(theme);\n            return createElement(DocsContainer, props);\n        }\n    }\n}\n```\n\u003c/details\u003e\n\n### 3. Use the theme in your app\n\nThere are two ways to use the theme in your app: with CSS custom properties, or with the `pvar` helper within JS/TS.\n\n#### CSS custom properties\n\nEach theme variable is exposed as a CSS custom property, which you can use in your stylesheets (including CSS Modules).\nThe custom property name is the same as the theme variable name, but prefixed with `--pte-` and with dots (`.`) replaced\nwith dashes (`-`).\n\nFor example, the `colors.primary` theme variable is exposed as `--pte-colors-primary`, which you can use in your\nstylesheets like so:\n\n```css\n/* styles.module.css */\n.h1 {\n    color: var(--pte-colors-primary);\n    letter-spacing: var(--pte-typography-h1-letterSpacing);\n}\n```\n\n#### `pvar` helper\n\nThe `pvar` helper is a function that allows you to access theme variables in your JavaScript/TypeScript code. It's\nuseful for dynamic/inline styling, or for usage in CSS-in-JS libraries.\n\nFor example, with inline styles:\n\n```tsx\n// components/MyComponent.tsx\nimport { pvar } from '../pte-init';\n\nexport function MyComponent() {\n    return (\n        \u003cdiv\n            style={{\n                // These path strings are type-safe, with IntelliSense autocompletion!\n                \n                color: pvar('colors.primary'),\n                // returns 'var(--pte-colors-primary)'\n                \n                letterSpacing: pvar('typography.h1.letterSpacing'),\n                // returns 'var(--pte-typography-h1-letterSpacing)'\n            }}\n        \u003e\n            Hello world!\n        \u003c/div\u003e\n    );\n}\n```\n\nOr with Emotion:\n\n```tsx\n// components/MyComponent.tsx\nimport { css } from '@emotion/react';\nimport { pvar } from '../pte-init';\n\nexport function MyComponent() {\n    return (\n        \u003cdiv\n            css={css`\n                color: ${pvar('colors.primary')};\n                letter-spacing: ${pvar('typography.h1.letterSpacing')};\n            `}\n        \u003e\n            Hello world!\n        \u003c/div\u003e\n    );\n}\n```\n\n### 4. Update the theme\n\nThe `updateTheme` function allows you to update the theme at runtime on the client side. It accepts a partial theme object, which overrides the existing theme.\n\nBecause the theme is stored in CSS custom properties, updating the theme doesn't cause any re-renders—the browser simply updates the variable values. This makes it far more performant than most other theming solutions, and also allows you to take advantage of CSS transitions when changing themes.\n\nYou can combine this with the `pget` helper, which allows you to access theme variables in your JavaScript/TypeScript code.\n\nFor example, in a React client component:\n\n```tsx\n// components/ThemeSwitcher.tsx\n\"use client\";\n\nimport { updateTheme, lightTheme, darkTheme } from '../pte-init';\n\nexport function ThemeSwitcher() {\n    return (\n        \u003cbutton\n            onClick={() =\u003e {\n                console.log(`The background color is currently ${pget('colors.backgroundPrimary')}`);                \n                updateTheme(\n                    pget('themeName') === 'light'\n                        ? darkTheme\n                        : lightTheme,\n                );\n            }}\n        \u003e\n            Switch to dark mode\n        \u003c/button\u003e\n    );\n}\n```\n\n#### Conditional styling in Server Components\n\nWith `pte`, you can also dispatch theme updates from the server side, and have the changes reflected in the client. This works by sending a simple `\u003cscript\u003e` that looks for the `#pte-vars` style object and updates it with the new theme values.\n\nFor example, in a Next.js `app` directory server component:\n\n```tsx\n// app/page.tsx\nimport { generateThemeInjection } from '../pte-init';\n\nexport default async function Home() {\n    // Fetch the user's theme preferences from the database on the server\n    const { themeName } = await fetchUserPreferences();\n    \n    // Select a theme based on the user's preferences\n    const theme = MyThemesList.find((t) =\u003e t.name === themeName);\n    \n    return (\n        \u003cmain\u003e\n            \u003ch1\u003eHello world!\u003c/h1\u003e\n            \u003cscript\n                type=\"text/javascript\"\n                dangerouslySetInnerHTML={{\n                    __html: generateThemeInjection(theme),\n                }}\n            /\u003e\n        \u003c/main\u003e\n    );\n}\n```\n\n#### Scoped updates and overrides\n\n`pte` is entirely based on CSS custom properties, which can be overridden at any scope. We can use that to our advantage by sending updated custom properties through inline styles that can be scoped to specific parts of the application.\n\nThis can be especially useful if you have an application where you want to offer advanced customization for specific users or tenants on components related to their account (e.g. a profile page), while maintaining your application's core styling across the rest of the application.\n\nFor example, in a Next.js `app` directory server component:\n\n```tsx\n// app/page.tsx\nimport { overrideTheme, MyThemesList } from '../pte-init';\n\nexport default async function Home() {\n    // Fetch the user's theme preferences from the database on the server\n    const { theme } = await fetchUserPreferences();\n    \n    // Select a theme based on the user's preferences\n    const selectedTheme = MyThemesList.find((t) =\u003e t.name === theme);\n    \n    return (\n        \u003cContainer\u003e\n            \u003ch1\u003eThis element inherits the application's default theme.\u003c/h1\u003e\n            \u003cProfileCard\n                id=\"home-page-container\"\n                style={overrideTheme(selectedTheme)}\n            \u003e\n                This card (and every element within it) is now styled with the user's theme!\n            \u003c/ProfileCard\u003e\n        \u003c/Container\u003e\n    )\n}\n```\n\nThen, on the client, you can use the `updateTheme` function anywhere to allow the user to update their theme within the client without needing to revalidate from the server.\n\nYou can also add the `selector` option for `pget` to access the theme variables in your client code specific to that scope (or any child element):\n\n```tsx\n// components/ThemeSwitcher.tsx\n\"use client\";\n\nimport { updateTheme, pget } from '../pte-init';\n\nexport function ThemeSwitcher() {\n    return (\n        \u003cbutton\n            onClick={() =\u003e {\n                console.log(`The background color is currently ${\n                    // `pget` accepts options for the element selector\n                    pget('colors.backgroundPrimary', {\n                        selector: '#home-page-container',\n                    })\n                }`);\n                \n                updateTheme(\n                    // `pget` uses `getComputedStyle` under the hood, so you can also read theme variables from any child element\n                    pget('themeName', {\n                        selector: '#any-child-of-container',\n                    }) === 'light'\n                        ? darkTheme\n                        : lightTheme,\n                );\n            }}\n        \u003e\n            Switch to dark mode\n        \u003c/button\u003e\n    );\n}\n```\n\n# Maintenance\n\n`pte` uses [pnpm](https://pnpm.io/) for package management. To install dependencies, run:\n\n```bash\npnpm install\n```\n\nTests are coming soon; currently planned for the 1.0.0 release.\n\nWe use [changesets](https://github.com/changesets/changesets) to manage releases. When contributing new changes, please run:\n\n```bash\npnpm changeset\n```\n\nThe CLI will ask you to enter brief descriptions of your changes and specify whether your changes are a patch, minor, or major (for semver). Once you've finished, commit the changeset files. The Changesets GitHub Actions automatically open a pull request with all current changeset files. When the PR is merged, the changesets will automatically be added to [CHANGELOG.md](./CHANGELOG.md), a new release tag will be created, and a new version will be published to npm.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslingshot%2Fpte","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fslingshot%2Fpte","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslingshot%2Fpte/lists"}