├── .gitattributes ├── README.md ├── packages └── catppuccin-unocss │ ├── .gitignore │ ├── tsconfig.json │ ├── typedoc.config.js │ ├── src │ ├── index.ts │ ├── types.ts │ └── extend │ │ ├── types.ts │ │ ├── index.ts │ │ └── index.test.ts │ ├── package.json │ └── README.md ├── example ├── .gitignore ├── public │ ├── preview.webp │ └── icons │ │ ├── icon-dark.svg │ │ └── icon-light.svg ├── tsconfig.json ├── astro.config.ts ├── package.json ├── uno.config.ts └── src │ ├── pages │ └── index.astro │ └── layout │ └── Main.astro ├── .gitignore ├── .github ├── .release-please-manifest.json ├── release-please-config.json └── workflows │ ├── ci.yaml │ ├── code-test.yaml │ ├── code-check.yaml │ ├── release-please.yaml │ └── publish-docs-and-website.yaml ├── .prettierignore ├── assets ├── previews │ ├── frappe.webp │ ├── latte.webp │ ├── mocha.webp │ ├── macchiato.webp │ └── preview.webp └── icons │ ├── icon-dark.svg │ └── icon-light.svg ├── pnpm-workspace.yaml ├── renovate.json ├── .vscode └── settings.json ├── prettier.config.js ├── .editorconfig ├── eslint.config.js ├── package.json ├── LICENCE └── tsconfig.base.json /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/catppuccin-unocss/README.md -------------------------------------------------------------------------------- /packages/catppuccin-unocss/.gitignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .astro/ 2 | 3 | .env.local 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | dist/ 4 | 5 | node_modules/ 6 | -------------------------------------------------------------------------------- /example/public/preview.webp: -------------------------------------------------------------------------------- 1 | ../../assets/previews/preview.webp -------------------------------------------------------------------------------- /example/public/icons/icon-dark.svg: -------------------------------------------------------------------------------- 1 | ../../../assets/icons/icon-dark.svg -------------------------------------------------------------------------------- /example/public/icons/icon-light.svg: -------------------------------------------------------------------------------- 1 | ../../../assets/icons/icon-light.svg -------------------------------------------------------------------------------- /.github/.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages/catppuccin-unocss": "3.0.0" 3 | } 4 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { "extends": ["../tsconfig.base.json", "astro/tsconfigs/strict"] } 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | 3 | .github/.release-please-manifest.json 4 | 5 | pnpm-lock.yaml 6 | -------------------------------------------------------------------------------- /assets/previews/frappe.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catppuccin/unocss/HEAD/assets/previews/frappe.webp -------------------------------------------------------------------------------- /assets/previews/latte.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catppuccin/unocss/HEAD/assets/previews/latte.webp -------------------------------------------------------------------------------- /assets/previews/mocha.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catppuccin/unocss/HEAD/assets/previews/mocha.webp -------------------------------------------------------------------------------- /assets/previews/macchiato.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catppuccin/unocss/HEAD/assets/previews/macchiato.webp -------------------------------------------------------------------------------- /assets/previews/preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catppuccin/unocss/HEAD/assets/previews/preview.webp -------------------------------------------------------------------------------- /packages/catppuccin-unocss/tsconfig.json: -------------------------------------------------------------------------------- 1 | { "extends": "../../tsconfig.base.json", "exclude": ["dist", "docs"] } 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - example 4 | 5 | onlyBuiltDependencies: 6 | - esbuild 7 | - sharp 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["local>catppuccin/renovate-config"] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.documentSelectors": ["**/*.astro"], 3 | "[typescript][javascript][astro][markdown][yaml][json][jsonc]": { 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/astro.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | 3 | import UnoCSS from 'unocss/astro'; 4 | 5 | export default defineConfig({ 6 | site: 'https://unocss.catppuccin.org', 7 | integrations: [UnoCSS()], 8 | }); 9 | -------------------------------------------------------------------------------- /.github/release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "last-release-sha": "2abd0ec82af934baa4296ed0f614f6c724c390fe", 4 | "include-component-in-tag": false, 5 | "packages": { 6 | "packages/catppuccin-unocss": { "package-name": "", "release-type": "node" } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/catppuccin-unocss/typedoc.config.js: -------------------------------------------------------------------------------- 1 | export default /** @type {Partial} */ ({ 2 | entryPoints: ['src'], 3 | entryPointStrategy: 'Expand', 4 | out: 'docs', 5 | cleanOutputDir: true, 6 | favicon: '../../assets/icons/icon-dark.svg', 7 | plugin: ['typedoc-material-theme'], 8 | themeColor: '#f38ba8', 9 | }); 10 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "scripts": { 5 | "dev": "astro dev", 6 | "build": "astro build", 7 | "preview": "astro preview" 8 | }, 9 | "dependencies": { 10 | "astro": "^5.13.5" 11 | }, 12 | "devDependencies": { 13 | "@catppuccin/palette": "2.0.0-beta.1", 14 | "unocss": "^66.5.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | export default /** @type {import('prettier').Options} */ ({ 2 | arrowParens: 'avoid', 3 | experimentalOperatorPosition: 'start', 4 | experimentalTernaries: true, 5 | jsxSingleQuote: true, 6 | objectWrap: 'collapse', 7 | singleQuote: true, 8 | plugins: ['prettier-plugin-astro'], 9 | overrides: [{ files: '*.astro', options: { parser: 'astro' } }], 10 | }); 11 | -------------------------------------------------------------------------------- /example/uno.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineConfig, 3 | presetWind4, 4 | transformerDirectives, 5 | transformerVariantGroup, 6 | } from 'unocss'; 7 | 8 | import presetCatppuccin from '../packages/catppuccin-unocss/src/index.ts'; 9 | import { flavorEntries } from '@catppuccin/palette'; 10 | 11 | export default defineConfig({ 12 | presets: [presetWind4({ preflights: { reset: true } }), presetCatppuccin()], 13 | transformers: [transformerDirectives(), transformerVariantGroup()], 14 | safelist: [...generateSafelist()], 15 | }); 16 | 17 | function generateSafelist() { 18 | return flavorEntries 19 | .flatMap(([, { colorEntries }]) => colorEntries) 20 | .map(([colourName]) => `bg-[--ctp-${colourName}]`); 21 | } 22 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # EditorConfig is awesome: https://EditorConfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | indent_size = 2 10 | indent_style = space 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | # go 16 | [*.go] 17 | indent_style = tab 18 | indent_size = 4 19 | 20 | # python 21 | [*.{ini,py,py.tpl,rst}] 22 | indent_size = 4 23 | 24 | # rust 25 | [*.rs] 26 | indent_size = 4 27 | 28 | # documentation, utils 29 | [*.{md,mdx,diff}] 30 | trim_trailing_whitespace = false 31 | 32 | # windows shell scripts 33 | [*.{cmd,bat,ps1}] 34 | end_of_line = crlf 35 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import { globalIgnores } from 'eslint/config'; 2 | 3 | import { default as js } from '@eslint/js'; 4 | import * as ts from 'typescript-eslint'; 5 | 6 | export default ts.config([ 7 | globalIgnores(['**/dist/**', '**/docs/**', '**/.astro']), 8 | { name: 'JavaScript', files: ['**/*.{m,}js'], ...js.configs.recommended }, 9 | { 10 | name: 'TypeScript', 11 | files: ['**/*.{m,}ts'], 12 | extends: [ts.configs.strictTypeChecked], 13 | rules: { 14 | '@typescript-eslint/no-unused-vars': [ 15 | 'error', 16 | { varsIgnorePattern: '^_' }, 17 | ], 18 | }, 19 | languageOptions: { 20 | parserOptions: { 21 | projectService: true, 22 | tsconfigRootDir: import.meta.dirname, 23 | }, 24 | }, 25 | }, 26 | ]); 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | run-name: ${{ inputs.reason }} 3 | 4 | on: 5 | push: 6 | paths: 7 | - .github/workflows/ci.yaml 8 | - packages/** 9 | - example/** 10 | - '**/package.json' 11 | - pnpm-lock.yaml 12 | pull_request: 13 | paths: 14 | - .github/workflows/ci.yaml 15 | - packages/** 16 | - example/** 17 | - '**/package.json' 18 | - pnpm-lock.yaml 19 | workflow_dispatch: 20 | inputs: 21 | reason: 22 | description: Dispatch reason 23 | type: string 24 | required: true 25 | 26 | jobs: 27 | code-check: 28 | name: Run Code Check Workflow 29 | uses: ./.github/workflows/code-check.yaml 30 | 31 | code-tests: 32 | name: Run Tests Workflow 33 | uses: ./.github/workflows/code-test.yaml 34 | -------------------------------------------------------------------------------- /.github/workflows/code-test.yaml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | runs-on: 7 | description: Runs on 8 | default: ubuntu-latest 9 | type: string 10 | node-version: 11 | description: Node.js version 12 | default: latest 13 | type: string 14 | 15 | jobs: 16 | run-tests: 17 | name: Run Tests 18 | runs-on: ${{ inputs.runs-on }} 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v5 22 | 23 | - name: Set-up pnpm 24 | uses: pnpm/action-setup@v4 25 | 26 | - name: Set-up Node.js ${{ inputs.node-version }} 27 | uses: actions/setup-node@v5 28 | with: 29 | node-version: ${{ inputs.node-version }} 30 | cache: pnpm 31 | 32 | - name: Install dependencies 33 | run: pnpm install 34 | 35 | - name: Run tests 36 | run: pnpm test 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "build": "pnpm -r build", 6 | "build:catppuccin-unocss": "pnpm -F @catppuccin/unocss build", 7 | "build:docs": "pnpm -F @catppuccin/unocss build:docs", 8 | "build:example": "pnpm -F example build", 9 | "dev": "pnpm -r dev", 10 | "dev:example": "pnpm -F example dev", 11 | "test": "pnpm -r test", 12 | "check:lint": "eslint .", 13 | "check:format": "prettier --check ." 14 | }, 15 | "devDependencies": { 16 | "@eslint/js": "^9.34.0", 17 | "@types/node": "^24.3.0", 18 | "eslint": "^9.34.0", 19 | "pkgroll": "^2.15.3", 20 | "prettier": "^3.6.2", 21 | "prettier-plugin-astro": "^0.14.1", 22 | "typedoc": "^0.28.12", 23 | "typedoc-material-theme": "^1.4.0", 24 | "typescript": "^5.9.2", 25 | "typescript-eslint": "^8.42.0" 26 | }, 27 | "packageManager": "pnpm@10.18.1", 28 | "engines": { 29 | "node": ">=20.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/code-check.yaml: -------------------------------------------------------------------------------- 1 | name: Check Code 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | runs-on: 7 | description: Runs on 8 | default: ubuntu-latest 9 | type: string 10 | node-version: 11 | description: Node.js version 12 | default: latest 13 | type: string 14 | 15 | jobs: 16 | code-check: 17 | name: Build and Lint Code 18 | runs-on: ${{ inputs.runs-on }} 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v5 22 | 23 | - name: Set-up pnpm 24 | uses: pnpm/action-setup@v4 25 | 26 | - name: Set-up Node.js ${{ inputs.node-version }} 27 | uses: actions/setup-node@v5 28 | with: 29 | node-version: ${{ inputs.node-version }} 30 | cache: pnpm 31 | 32 | - name: Install dependencies 33 | run: pnpm install 34 | 35 | - name: Build workspace packages 36 | run: pnpm build 37 | 38 | - name: Check linting 39 | run: pnpm check:lint 40 | -------------------------------------------------------------------------------- /assets/icons/icon-dark.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /assets/icons/icon-light.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Catppuccin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["**/dist/**"], 3 | 4 | "compilerOptions": { 5 | // Type Checking 6 | "allowUnreachableCode": false, 7 | "allowUnusedLabels": false, 8 | "exactOptionalPropertyTypes": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "noImplicitAny": true, 11 | "noImplicitOverride": true, 12 | "noImplicitReturns": true, 13 | "noImplicitThis": true, 14 | "noPropertyAccessFromIndexSignature": true, 15 | "noUncheckedIndexedAccess": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "strict": true, 19 | "useUnknownInCatchVariables": true, 20 | 21 | // Modules 22 | "allowImportingTsExtensions": true, 23 | "module": "Preserve", 24 | "moduleResolution": "bundler", 25 | "noUncheckedSideEffectImports": true, 26 | 27 | // Emit 28 | "noEmit": true, 29 | 30 | // Editor Support 31 | "checkJs": true, 32 | 33 | // Interop Constraints 34 | "isolatedModules": true, 35 | "verbatimModuleSyntax": true, 36 | 37 | // Language and Environment 38 | "emitDecoratorMetadata": true, 39 | "experimentalDecorators": true, 40 | "lib": ["ESNext"], 41 | "moduleDetection": "force", 42 | "target": "ESNext", 43 | 44 | // Output Formatting 45 | "noErrorTruncation": true, 46 | 47 | // Completeness 48 | "skipLibCheck": true 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/catppuccin-unocss/src/index.ts: -------------------------------------------------------------------------------- 1 | import { _extendTheme } from './extend/index.ts'; 2 | 3 | import type { Preset } from '@unocss/core'; 4 | 5 | import type { UnoCSSCatppuccinOptions } from './types.ts'; 6 | 7 | /** 8 | * Catppuccin preset for UnoCSS. 9 | * 10 | * @param options - Options for the preset 11 | * 12 | * @example 13 | * ```ts 14 | * // uno.config.ts 15 | * import { presetWind4, defineConfig } from 'unocss'; 16 | * import presetCatppuccin from '@catppuccin/unocss'; 17 | * 18 | * export default defineConfig({ 19 | * presets: [ 20 | * presetWind4(), 21 | * presetCatppuccin({ 22 | * // options 23 | * }) 24 | * ] 25 | * }); 26 | * ``` 27 | * 28 | * @see {@link https://unocss.catppuccin.com/docs} for documentation. 29 | */ 30 | export default ({ mode, ...rest }: UnoCSSCatppuccinOptions = {}) => { 31 | const preset: Preset = { name: '@catppuccin/unocss' }; 32 | 33 | switch (mode) { 34 | case 'extend': 35 | case undefined: { 36 | preset.extendTheme = _extendTheme(rest); 37 | break; 38 | } 39 | default: 40 | throw new CatppuccinUnoCSSError( 41 | `unsupported mode provided: '${String(mode)}'`, 42 | ); 43 | } 44 | 45 | return preset; 46 | }; 47 | 48 | class CatppuccinUnoCSSError extends Error { 49 | constructor(message: string) { 50 | super(message); 51 | 52 | this.name = '[@catppuccin/unocss] :: error :'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/catppuccin-unocss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@catppuccin/unocss", 3 | "version": "3.0.0", 4 | "description": "🌸 Soothing pastel theme preset for UnoCSS!", 5 | "keywords": [ 6 | "catppuccin", 7 | "theme", 8 | "unocss", 9 | "colours", 10 | "pastel" 11 | ], 12 | "homepage": "https://github.com/catppuccin/unocss#readme", 13 | "repository": "github:catppuccin/unocss", 14 | "funding": [ 15 | "https://github.com/sponsors/catppuccin", 16 | "https://opencollective.com/catppuccin" 17 | ], 18 | "license": "MIT", 19 | "author": "tuhana ", 20 | "type": "module", 21 | "exports": { 22 | ".": { 23 | "import": "./dist/index.js", 24 | "types": { 25 | "import": "./dist/index.d.ts" 26 | } 27 | } 28 | }, 29 | "module": "./dist/index.js", 30 | "types": "./dist/index.d.ts", 31 | "scripts": { 32 | "test": "node --test \"**/**.test.ts\"", 33 | "build": "pkgroll", 34 | "build:docs": "typedoc", 35 | "prepack": "pnpm build" 36 | }, 37 | "files": [ 38 | "dist/" 39 | ], 40 | "dependencies": { 41 | "@catppuccin/palette": "2.0.0-beta.1" 42 | }, 43 | "devDependencies": { 44 | "@unocss/core": "^66.5.0" 45 | }, 46 | "peerDependencies": { 47 | "@unocss/core": ">=0.51.0 <101" 48 | }, 49 | "peerDependenciesMeta": { 50 | "@unocss/core": { 51 | "optional": true 52 | } 53 | }, 54 | "engines": { 55 | "node": ">=20.0.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/catppuccin-unocss/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { PresetOptions } from '@unocss/core'; 2 | import type { ExtendOptions } from './extend/types'; 3 | 4 | /** 5 | * Available modes for this preset. 6 | */ 7 | export type Modes = 'extend'; 8 | 9 | /** 10 | * Options for a specific mode. 11 | * 12 | * @template T - The mode name or `undefined` 13 | * @template U - Options object for the specified mode 14 | * in `T`. 15 | * 16 | * @internal 17 | */ 18 | export type ModeOptions = 19 | T extends Modes ? 20 | { 21 | /** 22 | * Which mode to use this preset with. 23 | * 24 | * `extend` mode will extend the `theme` object to add 25 | * Catppuccin colours. A preset with CSS utilities 26 | * (e.g. [Wind4 Preset](https://unocss.dev/presets/wind4)) 27 | * MUST be used to be able to use the colours. 28 | * 29 | * @see {@link Modes} 30 | * 31 | * @default 'extend' 32 | */ 33 | mode: T; 34 | } & U 35 | : { mode?: undefined }; 36 | 37 | /** 38 | * Resolved options type based on the mode. 39 | * 40 | * @template T - The mode name or `undefined` 41 | * 42 | * @internal 43 | */ 44 | export type ResolvedModeOptions = 45 | T extends 'extend' ? ModeOptions : ModeOptions; 46 | 47 | /** 48 | * Options for this preset. 49 | */ 50 | export type UnoCSSCatppuccinOptions = PresetOptions 51 | & (ResolvedModeOptions<'extend'> | ResolvedModeOptions); 52 | -------------------------------------------------------------------------------- /packages/catppuccin-unocss/src/extend/types.ts: -------------------------------------------------------------------------------- 1 | import type { FlavorName } from '@catppuccin/palette'; 2 | 3 | export interface ExtendOptions { 4 | /** 5 | * Which `theme` object key to add the colours to. 6 | * 7 | * Some presets may use different keys to put things 8 | * under, such as `colours` or `shades`. Defaults 9 | * to what `presetWind4` uses. 10 | * 11 | * @default 'colors' 12 | */ 13 | themeKey?: string; 14 | 15 | /** 16 | * Prefix for using Catppuccin colours. 17 | * 18 | * When set to `false`, the prefix is removed and 19 | * colours are added directly. If a Catppuccin colour 20 | * conflicts with another preset's colour, former will 21 | * be prefixed under `ctp` instead of overriding. 22 | * 23 | * @example 24 | * ```html 25 | * 26 | *

Hello world!

27 | * 28 | *

Hello world!

29 | * 31 | *

Hello world!

32 | * ``` 33 | * 34 | * @default 'ctp' 35 | */ 36 | prefix?: string | false; 37 | 38 | /** 39 | * Default flavour to use colours from. 40 | * 41 | * ```html 42 | * 43 | *

Hello world!

44 | * 45 | *

Hello world!

46 | * 48 | *

Hello world!

49 | * ``` 50 | */ 51 | defaultFlavour?: FlavorName; 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yaml: -------------------------------------------------------------------------------- 1 | name: Release Please 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | release-please: 11 | name: Run Release Please 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | issues: write 16 | pull-requests: write 17 | outputs: 18 | release-created: ${{ steps.release-please.outputs['packages/catppuccin-unocss--release_created'] }} 19 | tag-name: ${{ steps.release-please.outputs['packages/catppuccin-unocss--tag_name'] }} 20 | steps: 21 | - name: Run Release Please Action 22 | id: release-please 23 | uses: googleapis/release-please-action@v4 24 | with: 25 | token: ${{ github.token }} 26 | config-file: .github/release-please-config.json 27 | manifest-file: .github/.release-please-manifest.json 28 | 29 | publish-packages: 30 | name: Publish Packages 31 | runs-on: ubuntu-latest 32 | needs: release-please 33 | permissions: 34 | contents: read 35 | id-token: write 36 | if: needs.release-please.outputs.release-created == 'true' 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v5 40 | 41 | - name: Set-up pnpm 42 | uses: pnpm/action-setup@v4 43 | 44 | - name: Set-up Node.js 45 | uses: actions/setup-node@v5 46 | with: 47 | node-version: latest 48 | registry-url: https://registry.npmjs.org 49 | cache: pnpm 50 | 51 | - name: Install dependencies 52 | run: pnpm install 53 | 54 | - name: Publish to npm 55 | run: pnpm publish -F @catppuccin/unocss --access public --provenance 56 | env: 57 | NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} 58 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs-and-website.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Documentation and Example Website 2 | 3 | on: 4 | workflow_run: 5 | workflows: 6 | - CI 7 | - Release Please 8 | types: 9 | - completed 10 | 11 | permissions: 12 | contents: read 13 | pages: write 14 | id-token: write 15 | 16 | concurrency: 17 | group: pages 18 | cancel-in-progress: false 19 | 20 | jobs: 21 | publish: 22 | name: Publish Documentation and Example Website 23 | runs-on: ubuntu-latest 24 | if: github.event.workflow_run.conclusion == 'success' 25 | environment: 26 | name: github-pages 27 | url: ${{ steps.deploy.outputs.page_url }} 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v5 31 | 32 | - name: Set up GitHub Pages 33 | uses: actions/configure-pages@v5 34 | 35 | - name: Set up pnpm 36 | uses: pnpm/action-setup@v4 37 | 38 | - name: Set up Node.js 39 | uses: actions/setup-node@v5 40 | with: 41 | node-version: latest 42 | cache: pnpm 43 | 44 | - name: Install dependencies 45 | run: pnpm install 46 | 47 | - name: Build workspace packages and documentation 48 | run: | 49 | pnpm build 50 | pnpm build:docs 51 | 52 | - name: Prepare structure 53 | run: | 54 | mkdir -p github-pages/docs 55 | cp -r packages/catppuccin-unocss/docs/* github-pages/docs/ 56 | cp -r example/dist/* github-pages/ 57 | 58 | - name: Upload artifacts 59 | uses: actions/upload-pages-artifact@v4 60 | with: 61 | path: github-pages 62 | 63 | - name: Deploy to GitHub Pages 64 | id: deploy 65 | uses: actions/deploy-pages@v4 66 | with: 67 | preview: ${{ github.event.workflow_run.event == 'CI' }} 68 | -------------------------------------------------------------------------------- /packages/catppuccin-unocss/src/extend/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | flavorEntries, 3 | flavors, 4 | type CatppuccinFlavor, 5 | } from '@catppuccin/palette'; 6 | 7 | import type { ThemeExtender } from '@unocss/core'; 8 | 9 | import type { ExtendOptions } from './types.ts'; 10 | 11 | /** 12 | * Extend the `theme` object of UnoCSS by passing this 13 | * function to its `extendTheme` option. 14 | * 15 | * @param options - Options for the extender 16 | */ 17 | export const _extendTheme = (options: ExtendOptions = {}) => { 18 | const { themeKey = 'colors', prefix = 'ctp', defaultFlavour } = options; 19 | 20 | /** 21 | * Adds Catppuccin colours to the target object. 22 | * 23 | * @param targetObj - Target theme object to extend 24 | * @param flavour - The Catppuccin flavour object 25 | * @param namespace - Optional namespace to nest colours under 26 | */ 27 | const addFlavourColours = ( 28 | targetObj: ThemeColoursObject, 29 | flavour: CatppuccinFlavor, 30 | namespace?: string, 31 | ) => { 32 | const colourEntries = Object.entries(flavour.colors); 33 | let targetContainer: ThemeColoursObject; 34 | 35 | if (namespace) { 36 | if ( 37 | targetObj[namespace] !== undefined 38 | && typeof targetObj[namespace] !== 'object' 39 | ) { 40 | if (!targetObj['ctp']) targetObj['ctp'] = {}; 41 | const targetObjCtp = targetObj['ctp'] as ThemeColoursObject; 42 | 43 | if (!targetObjCtp[namespace]) targetObjCtp[namespace] = {}; 44 | targetContainer = targetObjCtp[namespace] as ThemeColoursObject; 45 | } else { 46 | if (!targetObj[namespace]) targetObj[namespace] = {}; 47 | targetContainer = targetObj[namespace]; 48 | } 49 | } else if (prefix) { 50 | targetContainer = targetObj; 51 | } else { 52 | targetContainer = targetObj; 53 | } 54 | 55 | for (const [colourName, colourData] of colourEntries) { 56 | if (!prefix && colourName in targetObj) { 57 | if (!targetObj['ctp']) targetObj['ctp'] = {}; 58 | const targetObjCtp = targetObj['ctp'] as ThemeColoursObject; 59 | 60 | targetObjCtp[colourName] = colourData.hex; 61 | } else { 62 | targetContainer[colourName] = colourData.hex; 63 | } 64 | } 65 | }; 66 | 67 | return (baseTheme => { 68 | const theme = baseTheme as Record; 69 | 70 | if (!theme[themeKey]) theme[themeKey] = {}; 71 | 72 | let targetObject = theme[themeKey]; 73 | if (prefix) { 74 | if (!targetObject[prefix]) targetObject[prefix] = {}; 75 | targetObject = targetObject[prefix] = targetObject[ 76 | prefix 77 | ] as ThemeColoursObject; 78 | } 79 | 80 | if (defaultFlavour && defaultFlavour in flavors) { 81 | addFlavourColours(targetObject, flavors[defaultFlavour]); 82 | } else { 83 | for (const [flavourIdentifier, flavour] of flavorEntries) { 84 | addFlavourColours(targetObject, flavour, flavourIdentifier); 85 | } 86 | } 87 | }) satisfies ThemeExtender; 88 | }; 89 | 90 | /** 91 | * Nested object structure for theme colours. 92 | * 93 | * @internal 94 | */ 95 | export interface ThemeColoursObject { 96 | [key: string]: ThemeColoursObject | string; 97 | } 98 | -------------------------------------------------------------------------------- /packages/catppuccin-unocss/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Catppuccin Logo 5 |
6 | Catppuccin for UnoCSS 7 |

8 | 9 |

10 | 11 | Stars Count Badge 12 | 13 | Issues Count Badge 14 | 15 | Contributors Count Badge 16 |

17 | 18 |

19 | Preview 20 |

21 | 22 | ## Previews 23 | 24 |
25 | 🌻 Latte 26 | Latte Preview 27 |
28 |
29 | 🪴 Frappé 30 | Frappé Preview 31 |
32 |
33 | 🌺 Macchiato 34 | Macchiato Preview 35 |
36 |
37 | 🌿 Mocha 38 | Mocha Preview 39 |
40 | 41 | ## Usage 42 | 43 | 1. Install the npm package `@catppuccin/unocss` 44 | 45 | ```sh 46 | npm install -D @catppuccin/unocss 47 | # or 48 | pnpm add -D @catppuccin/unocss 49 | # or 50 | yarn add -D @catppuccin/unocss 51 | ``` 52 | 53 | 2. Add the preset to your UnoCSS configuration 54 | 55 | ```ts 56 | // uno.config.ts 57 | import { presetWind4, defineConfig } from 'unocss'; 58 | import presetCatppuccin from '@catppuccin/unocss'; 59 | 60 | export default defineConfig({ 61 | presets: [ 62 | presetWind4(), 63 | presetCatppuccin({ 64 | // options 65 | }) 66 | ] 67 | }); 68 | ``` 69 | 70 | 3. Configure the preset if needed. Refer to the [FAQ](#-faq) for documentation 71 | 72 | ## 🙋 FAQ 73 | 74 | - Q: Where can I find the documentation?\ 75 | A: Documentation can be found at 76 | 77 | ## 💝 Thanks to 78 | 79 | - [tuhana](https://github.com/catuhana) 80 | 81 |   82 | 83 |

84 | Catppuccin Line Separator 85 |

86 | 87 |

88 | Copyright © 2021-present Catppuccin Org 89 |

90 | 91 |

92 | 93 | Licence Badge 94 | 95 |

96 | -------------------------------------------------------------------------------- /example/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Main from '../layout/Main.astro'; 3 | 4 | import { flavorEntries } from '@catppuccin/palette'; 5 | 6 | const { 7 | flavourNames, 8 | flavourEmojis, 9 | flavourPrettyNames, 10 | accentColourNames, 11 | neutralColourNames, 12 | } = flavorEntries.reduce( 13 | (acc, [flavourId, flavour], index) => { 14 | acc.flavourNames.push(flavourId); 15 | acc.flavourEmojis.push(flavour.emoji); 16 | acc.flavourPrettyNames.push(flavour.name); 17 | 18 | if (index === 0) { 19 | flavour.colorEntries.forEach(([colourId, colourData]) => { 20 | (colourData.accent ? 21 | acc.accentColourNames 22 | : acc.neutralColourNames 23 | ).push(colourId); 24 | }); 25 | } 26 | 27 | return acc; 28 | }, 29 | { 30 | flavourNames: [] as string[], 31 | flavourEmojis: [] as string[], 32 | flavourPrettyNames: [] as string[], 33 | accentColourNames: [] as string[], 34 | neutralColourNames: [] as string[], 35 | }, 36 | ); 37 | --- 38 | 39 |
40 |
41 |
42 |
43 |

44 | Catppuccin 45 |

46 |
    47 | { 48 | [ 49 | ['Documentation', `/docs`], 50 | ['GitHub', 'https://github.com/catppuccin/unocss'], 51 | ['npm', 'https://www.npmjs.com/package/@catppuccin/unocss'], 52 | ].map(([text, href]) => ( 53 |
  • 54 | 59 | {text} 60 | 61 |
  • 62 | )) 63 | } 64 |
65 |
66 |
67 |
68 |
69 |

Flavours

70 |
71 | { 72 | flavourNames.map((flavour, index) => ( 73 | 89 | )) 90 | } 91 |
92 |
93 |
94 |

Accent Colours

95 |
96 | { 97 | accentColourNames.map(colour => ( 98 | 109 | )) 110 | } 111 |
112 |
113 |
114 |

Neutral Colours

115 |
116 | { 117 | neutralColourNames.map(colour => { 118 | return ( 119 | 130 | ); 131 | }) 132 | } 133 |
134 |
135 |
136 |
137 |
138 | 139 | 167 | 168 | 214 | -------------------------------------------------------------------------------- /packages/catppuccin-unocss/src/extend/index.test.ts: -------------------------------------------------------------------------------- 1 | import { suite, test, type TestContext } from 'node:test'; 2 | import { 3 | flavorEntries, 4 | flavors, 5 | type CatppuccinFlavor, 6 | type FlavorName, 7 | } from '@catppuccin/palette'; 8 | 9 | import { _extendTheme, type ThemeColoursObject } from './index.ts'; 10 | import type { ExtendOptions } from './types.ts'; 11 | 12 | await suite('_extendTheme', async () => { 13 | const flavourNames = Object.keys(flavors) as FlavorName[]; 14 | 15 | const themeKeyOptions = [undefined, 'colors', 'tones'] as const; 16 | const prefixOptions = [undefined, 'ctp', 'meow', false] as const; 17 | const defaultFlavourOptions = [undefined, ...flavourNames] as const; 18 | 19 | await suite('option combinations', async () => { 20 | for (const themeKey of themeKeyOptions) { 21 | for (const prefix of prefixOptions) { 22 | for (const defaultFlavour of defaultFlavourOptions) { 23 | const options: ExtendOptions = {}; 24 | 25 | if (themeKey !== undefined) options.themeKey = themeKey; 26 | if (prefix !== undefined) options.prefix = prefix; 27 | if (defaultFlavour !== undefined) 28 | options.defaultFlavour = defaultFlavour; 29 | 30 | const optionDescription = Object.entries(options) 31 | .map(([key, value]) => `${key}=${JSON.stringify(value)}`) 32 | .join(', '); 33 | 34 | await test(optionDescription || 'default options', test => { 35 | const theme = {}; 36 | _extendTheme(options)(theme); 37 | validateTheme(theme, options)(test); 38 | }); 39 | } 40 | } 41 | } 42 | }); 43 | 44 | await test("conflicting keys don't override existing keys", (test: TestContext) => { 45 | const theme: { colors: { red: string; ctp?: Record } } = { 46 | colors: { red: 'meow' }, 47 | }; 48 | _extendTheme({ prefix: false, defaultFlavour: 'mocha' })(theme); 49 | 50 | test.assert.equal( 51 | theme.colors.red, 52 | 'meow', 53 | 'Existing colour should not be overridden', 54 | ); 55 | test.assert.ok( 56 | theme.colors.ctp?.['red'], 57 | 'Catppuccin colour should be added under ctp prefix', 58 | ); 59 | }); 60 | 61 | await test('handles existing ctp key correctly', (test: TestContext) => { 62 | const theme: { colors: { ctp: { custom: string; mocha?: unknown } } } = { 63 | colors: { ctp: { custom: 'value' } }, 64 | }; 65 | _extendTheme({})(theme); 66 | 67 | test.assert.equal( 68 | theme.colors.ctp.custom, 69 | 'value', 70 | 'Existing ctp.custom should be preserved', 71 | ); 72 | test.assert.ok( 73 | theme.colors.ctp.mocha, 74 | 'Catppuccin mocha flavour should be added', 75 | ); 76 | }); 77 | 78 | await test("generated colours are accurate to Catppuccin's palette", (test: TestContext) => { 79 | const theme = {} as { 80 | colors: { ctp: Record> }; 81 | }; 82 | _extendTheme({})(theme); 83 | 84 | for (const [flavourName, flavour] of flavorEntries) { 85 | const themeFlavour = theme.colors.ctp[flavourName]; 86 | test.assert.ok( 87 | typeof themeFlavour === 'object', 88 | `'${flavourName}' should be an object`, 89 | ); 90 | 91 | for (const [colourName, colourData] of Object.entries(flavour.colors)) { 92 | test.assert.equal( 93 | themeFlavour[colourName], 94 | colourData.hex, 95 | `'${flavourName}.${colourName}' should have correct hex value`, 96 | ); 97 | } 98 | } 99 | }); 100 | 101 | function validateTheme(theme: ThemeColoursObject, options: ExtendOptions) { 102 | return (test: TestContext) => { 103 | const { themeKey = 'colors', prefix = 'ctp', defaultFlavour } = options; 104 | 105 | test.assert.ok(theme, 'Theme should exist'); 106 | test.assert.ok(theme[themeKey], `Theme should have a '${themeKey}' key`); 107 | 108 | const targetObj = theme[themeKey] as ThemeColoursObject; 109 | const prefixContainer = ( 110 | prefix === false ? targetObj : targetObj[prefix]) as ThemeColoursObject; 111 | 112 | test.assert.ok( 113 | prefixContainer, 114 | `Theme colours should exist${prefix === false ? '' : ` under '${prefix}' prefix`}`, 115 | ); 116 | 117 | if (defaultFlavour) { 118 | validateFlavourColours( 119 | prefixContainer, 120 | flavors[defaultFlavour], 121 | prefix === false, 122 | )(test); 123 | } else { 124 | for (const [flavourName, flavour] of flavorEntries) { 125 | test.assert.ok( 126 | prefixContainer[flavourName], 127 | `Flavour '${flavourName}' should exist in theme`, 128 | ); 129 | 130 | validateFlavourStructure( 131 | prefixContainer[flavourName] as ThemeColoursObject, 132 | flavour, 133 | flavourName, 134 | )(test); 135 | } 136 | } 137 | }; 138 | } 139 | 140 | function validateFlavourStructure( 141 | flavourObj: ThemeColoursObject, 142 | flavour: CatppuccinFlavor, 143 | flavourName: string, 144 | ) { 145 | return (test: TestContext) => { 146 | test.assert.ok( 147 | typeof flavourObj === 'object', 148 | `'${flavourName}' should be an object`, 149 | ); 150 | 151 | const expectedColourNames = Object.keys(flavour.colors).sort(); 152 | const actualColourNames = Object.keys(flavourObj).sort(); 153 | 154 | test.assert.deepEqual( 155 | actualColourNames, 156 | expectedColourNames, 157 | `Colours in '${flavourName}' should match expected colour names`, 158 | ); 159 | 160 | for (const [colourName, colourData] of Object.entries(flavour.colors)) { 161 | test.assert.equal( 162 | flavourObj[colourName], 163 | colourData.hex, 164 | `'${flavourName}.${colourName}' should have correct hex value`, 165 | ); 166 | } 167 | }; 168 | } 169 | 170 | function validateFlavourColours( 171 | container: ThemeColoursObject, 172 | flavour: CatppuccinFlavor, 173 | checkFallback: boolean, 174 | ) { 175 | return (test: TestContext) => { 176 | for (const [colourName, colourData] of Object.entries(flavour.colors)) { 177 | const colourValue = 178 | container[colourName] 179 | ?? (checkFallback 180 | && (container['ctp'] as ThemeColoursObject)[colourName]); 181 | 182 | test.assert.ok( 183 | colourValue !== undefined, 184 | `Colour '${colourName}' should exist in theme`, 185 | ); 186 | 187 | if (container[colourName]) { 188 | test.assert.equal( 189 | container[colourName], 190 | colourData.hex, 191 | `Colour '${colourName}' should have correct hex value`, 192 | ); 193 | } else if ( 194 | checkFallback 195 | && (container['ctp'] as ThemeColoursObject)[colourName] 196 | ) { 197 | test.assert.equal( 198 | (container['ctp'] as ThemeColoursObject)[colourName], 199 | colourData.hex, 200 | `Fallback colour 'ctp.${colourName}' should have correct hex value`, 201 | ); 202 | } 203 | } 204 | }; 205 | } 206 | }); 207 | -------------------------------------------------------------------------------- /example/src/layout/Main.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { 3 | meta: { 4 | title = 'Catppuccin for UnoCSS', 5 | description = '🌸 Soothing pastel theme for UnoCSS!', 6 | } = {}, 7 | theme = '🌻', 8 | } = Astro.props as { 9 | meta?: { title?: string; description?: string }; 10 | theme?: string; 11 | }; 12 | --- 13 | 14 | 15 | 16 | 17 | 18 | 19 | {title} 20 | 21 | {/* Information */} 22 | 23 | 24 | 25 | {/* Meta */} 26 | 27 | 32 | 33 | 34 | 35 | {/* OpenGraph */} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {/* Twitter */} 44 | 45 | 46 | 47 | 48 | 49 | 50 | 57 | 64 | 65 | 68 | 69 | 70 | 71 | 72 | 206 | --------------------------------------------------------------------------------