{"id":16792477,"url":"https://github.com/ivanhofer/sthemer","last_synced_at":"2025-03-22T01:30:37.851Z","repository":{"id":37413426,"uuid":"500106175","full_name":"ivanhofer/sthemer","owner":"ivanhofer","description":"A lightweight yet powerful solution to support multiple color schemes in your Svelte/SvelteKit application.","archived":false,"fork":false,"pushed_at":"2023-06-28T01:10:14.000Z","size":577,"stargazers_count":27,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-18T06:51:24.151Z","etag":null,"topics":["color-schemes","dark-mode","light-mode","svelte","sveltekit","theme","themes","theming"],"latest_commit_sha":null,"homepage":"sthemer.vercel.app","language":"Svelte","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ivanhofer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"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},"funding":{"github":["ivanhofer"]}},"created_at":"2022-06-05T13:31:28.000Z","updated_at":"2024-05-29T06:50:49.000Z","dependencies_parsed_at":"2024-10-28T12:17:49.667Z","dependency_job_id":"816dd8a7-d00b-450c-8b2b-29adb454365e","html_url":"https://github.com/ivanhofer/sthemer","commit_stats":{"total_commits":114,"total_committers":2,"mean_commits":57.0,"dds":0.368421052631579,"last_synced_commit":"d1d09f1e4373c085373ddd7a83122c9e7bfe8cb6"},"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivanhofer%2Fsthemer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivanhofer%2Fsthemer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivanhofer%2Fsthemer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivanhofer%2Fsthemer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ivanhofer","download_url":"https://codeload.github.com/ivanhofer/sthemer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244893317,"owners_count":20527571,"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":["color-schemes","dark-mode","light-mode","svelte","sveltekit","theme","themes","theming"],"created_at":"2024-10-13T08:46:06.530Z","updated_at":"2025-03-22T01:30:37.568Z","avatar_url":"https://github.com/ivanhofer.png","language":"Svelte","funding_links":["https://github.com/sponsors/ivanhofer"],"categories":[],"sub_categories":[],"readme":"# :first_quarter_moon: sthemer\n\n**A lightweight yet powerful solution to support multiple color schemes in your Svelte/SvelteKit application.**\n\n## Advantages\n\n:baby_chick: lightweight (\u003c1kb)\\\n:muscle: powerful\\\n:ok_hand: easy to use\\\n:running: fast and efficient\\\n:nesting_dolls: nested schemes \\\n:stopwatch: supports SSR (Server-Side Rendering)\\\n:safety_vest: best TypeScript support (works with JavaScript projects too) \\\n:no_entry: no external dependencies\n\n\u003c!-- list of supported emojis on GitHub: https://github.com/ikatyang/emoji-cheat-sheet --\u003e\n\n## Examples\n\n[Click here to see some live examples](https://sthemer.vercel.app/)\n\n## Getting Started\n\n1. :keyboard: Install `sthemer` as a dependency.\n\n   ```bash\n   npm install sthemer\n   ```\n\n2. :wrench: Add the style mixin globally.\n\n   _svelte.config.js_\n\n   ```js\n   import preprocess from 'svelte-preprocess'\n\n   /** @type {import('@sveltejs/kit').Config} */\n   const config = {\n      preprocess: preprocess({\n         scss: {\n            prependData: `@import 'sthemer/mixins';`,\n         },\n      }),\n   }\n\n   export default config\n   ```\n\n   \u003e This example uses [`scss`](https://sass-lang.com/documentation/syntax#scss) as css preprocessor. Other formats are also supported. See the [supported CSS preprocessors](#supported-css-preprocessors) section for more details.\n\n3. :file_folder: Wrap your code with the `Sthemer.svelte` component and define your preferred [strategy](#strategies).\n\n   _App.svelte_ or \\_\\__layout.svelte_\n\n   ```svelte\n   \u003cscript\u003e\n      import Sthemer from 'sthemer/Sthemer.svelte'\n   \u003c/script\u003e\n\n   \u003cSthemer strategy=\"auto\"\u003e\n      \u003c!-- your application goes here --\u003e\n   \u003c/Sthemer\u003e\n   ```\n\n4. :art: Define styles for your components.\n\n   _Button.svelte_\n\n   ```svelte\n   \u003cbutton on:click\u003e\n      \u003cslot /\u003e\n   \u003c/button\u003e\n\n   \u003cstyle lang=\"scss\"\u003e\n      button {\n         // styles that apply to both schemes\n         font-size: 1.3rem;\n         padding: 10px 20px;\n         border-radius: 8px;\n\n         // these styles will apply when the component gets rendered on a 'dark' wrapper\n         @include on-dark {\n            background-color: white;\n            color: black;\n         }\n\n         // these styles will apply when the component gets rendered on a 'light' wrapper\n         @include on-light {\n            background-color: black;\n            color: white;\n         }\n      }\n   \u003c/style\u003e\n   ```\n\n5. :open_book: Thats it. Play around and explore the docs to see some more examples.\n\n6. :star: Star this project on [GitHub](https://github.com/ivanhofer/sthemer).\n   \u003e Thanks! This helps the project to grow.\n\n## Usage\n\n#### Sthemer.svelte\n\nTo wrap your application or parts of it. It will set up everything in order for you to just [define the styles](#supported-css-preprocessors) of your components.\n\n```svelte\n\u003cscript\u003e\n   import Sthemer from 'sthemer/Sthemer.svelte'\n\u003c/script\u003e\n\n\u003cSthemer strategy=\"auto\" let:scheme\u003e\n   I am rendered on a \u003cstrong\u003e{scheme}\u003c/strong\u003e\n   wrapper.\n\u003c/Sthemer\u003e\n```\n\n##### props\n\n-  `strategy` (optional): the [strategy](#strategies) to use.\\\n    The component reacts to changes to the `strategy` prop and changes the color scheme accordingly.\n\n#### slot props\n\n-  `let:scheme`: the current used scheme.\\\n    To get the current used scheme, use the `let:scheme` slot prop.\n\n#### context.ts\n\nTo programmatically access the current used scheme and strategy, you can call `getSthemerContext()`. The returned object contains two items:\n\n-  **strategy**: a [`writable-store`](https://svelte.dev/docs#run-time-svelte-store-writable) containing the strategy to use.\n-  **scheme**: a [`readable-store`](https://svelte.dev/docs#run-time-svelte-store-readable) containing the used color scheme.\n\n```svelte\n\u003cscript\u003e\n   import { getSthemerContext } from 'sthemer/context'\n   const { strategy, scheme } = getSthemerContext()\n\n   const toggleStrategy = () =\u003e {\n      $strategy = $strategy === 'dark' ? 'light' : 'dark'\n   }\n\u003c/script\u003e\n\n\u003cbutton on:click={toggleStrategy}\u003eToggle strategy\u003c/button\u003e\n\nUsed scheme: {$scheme}\n```\n\n### supported CSS preprocessors\n\n`sthemer` works with the most used CSS preprocessors to provide you with a good user experience. It is recommended to use one of the following options.\n\n#### [scss](https://sass-lang.com/documentation/syntax#scss)\n\n1. Add the mixin globally.\n\n   _svelte.config.js_\n\n   ```js\n   import preprocess from 'svelte-preprocess'\n\n   /** @type {import('@sveltejs/kit').Config} */\n   const config = {\n      preprocess: preprocess({\n         scss: {\n            prependData: `@import 'sthemer/mixins';`,\n         },\n      }),\n   }\n\n   export default config\n   ```\n\n2. Define styles for your components.\n\n   _Component.svelte_\n\n   ```svelte\n   \u003cbutton on:click\u003e\n      \u003cslot /\u003e\n   \u003c/button\u003e\n\n   \u003cstyle lang=\"scss\"\u003e\n      button {\n         // these styles will apply when the component gets rendered on a 'dark' wrapper\n         @include on-dark {\n            background-color: white;\n            color: black;\n         }\n\n         // these styles will apply when the component gets rendered on a 'light' wrapper\n         @include on-light {\n            background-color: black;\n            color: white;\n         }\n      }\n   \u003c/style\u003e\n   ```\n\n#### [less](https://lesscss.org/)\n\n1. Add the mixin globally.\n\n   _svelte.config.js_\n\n   ```js\n   import preprocess from 'svelte-preprocess'\n\n   /** @type {import('@sveltejs/kit').Config} */\n   const config = {\n      preprocess: preprocess({\n         less: {\n            prependData: `@import 'sthemer/mixins';`,\n         },\n      }),\n   }\n\n   export default config\n   ```\n\n2. Define styles for your components.\n\n   _Component.svelte_\n\n   ```svelte\n   \u003cbutton on:click\u003e\n      \u003cslot /\u003e\n   \u003c/button\u003e\n\n   \u003cstyle lang=\"less\"\u003e\n      button {\n         // these styles will apply when the component gets rendered on a 'dark' wrapper\n         .on-dark({\n            background-color: white;\n            color: black;\n         });\n\n         // these styles will apply when the component gets rendered on a 'light' wrapper\n         .on-light({\n            background-color: black;\n            color: white;\n         });\n      }\n   \u003c/style\u003e\n   ```\n\n#### [sass](https://sass-lang.com/)\n\n1. Add the mixin globally.\n\n   _svelte.config.js_\n\n   ```js\n   import preprocess from 'svelte-preprocess'\n\n   /** @type {import('@sveltejs/kit').Config} */\n   const config = {\n      preprocess: preprocess({\n         sass: {\n            prependData: `@import 'sthemer/mixins'`,\n         },\n      }),\n   }\n\n   export default config\n   ```\n\n2. Define styles for your components.\n\n   _Component.svelte_\n\n   ```svelte\n   \u003cbutton on:click\u003e\n      \u003cslot /\u003e\n   \u003c/button\u003e\n\n   \u003cstyle lang=\"sass\"\u003e\n      button\n         // these styles will apply when the component gets rendered on a 'dark' wrapper\n         @include on-dark\n            background-color: white\n            color: black\n   \n         // these styles will apply when the component gets rendered on a 'light' wrapper\n         @include on-light\n            background-color: black\n            color: white\n   \u003c/style\u003e\n   ```\n\n#### CSS (no preprocessor)\n\n1. Define styles for your components.\n\n   _Component.svelte_\n\n   ```svelte\n   \u003cbutton on:click\u003e\n      \u003cslot /\u003e\n   \u003c/button\u003e\n\n   \u003cstyle\u003e\n      // these styles will apply when the component gets rendered on a 'dark' wrapper\n      :global(.sthemer-dark) button {\n         background-color: white;\n         color: black;\n      }\n\n      // these styles will apply when the component gets rendered on a 'light' wrapper\n      :global(.sthemer-dark) button {\n         background-color: black;\n         color: white;\n      }\n   \u003c/style\u003e\n   ```\n\n### Schemes\n\n`sthemer` supports the [built-in color schemes](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme) **'dark'** and **'light'**.\n\n-  **light**: The user expects a light background and dark text.\n-  **dark**: The user expects a dark background and light text.\n\n### Strategies\n\n-  #### **auto**:\n\n   **default value**\n\n   Auto-detects the user's preferred color scheme.\n\n   The [prefers-color-scheme media query](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) is used to determine the user's preferred color scheme.\n\n   \u003e Read [here](#server-side-rendering-ssr) to know how to add SSR support.\n\n-  #### **light**:\n\n   Use the **light** color scheme.\n\n-  #### **dark**:\n\n   Use the **dark** color scheme.\n\n-  #### **inverted**:\n\n   Use the **inverted** color scheme.\n\n   If used on a **light** color scheme, it will be **dark** and vice versa. Can be useful when using [nested schemes](#nested-schemes). When used at the root, it uses the inverted color scheme from the ['auto'-strategy](#auto).\n\n   \u003e Read [here](#server-side-rendering-ssr) to know how to add SSR support.\n\n### Nested Schemes\n\nBy default `sthemer` doesn't output code that can be used with nested color schemes. But you can manually specify how many levels of nesting you want support.\n\n-  [**scss**](#scss)\n\n   -  globally\n\n      _svelte.config.js_\n\n      ```js\n      // set the '$sthemerLevels' variable to the value you want\n      prependData: '@import 'sthemer/mixins'; $sthemerLevels: 3',\n      ```\n\n   -  for a specific selector\n\n      _Component.svelte_\n\n      ```scss\n      button {\n         // add the amount of levels as a parameter\n         @include on-dark(3) {\n            background-color: white;\n         }\n      }\n      ```\n\n-  [**less**](#less)\n\n   -  globally\n\n      _svelte.config.js_\n\n      ```js\n      // set the '@sthemerLevels' variable to the value you want\n      prependData: `@import 'sthemer/mixins'; @sthemerLevels: 3;`,\n      ```\n\n   -  for a specific selector\n\n      _Component.svelte_\n\n      ```less\n      button {\n         .on-dark({\n            background-color: white;\n         }, 3); // add the amount of levels as a second parameter\n      }\n      ```\n\n-  [**sass**](#sass)\n\n   -  globally\n\n      _svelte.config.js_\n\n      ```js\n      // set the '$sthemerLevels' variable to the value you want\n      prependData: `\n      @import 'sthemer/mixins'\n      $sthemerLevels: 3\n      `,\n      ```\n\n   -  for a specific selector\n\n      _Component.svelte_\n\n      ```scss\n      button\n         // add the amount of levels as a parameter\n         @include on-dark(3)\n            background-color: white\n      ```\n\n-  [**CSS**](#css-no-preprocessor)\n\n   If you want to support multiple [levels of nesting](#nested-schemes) with plain CSS, you need to manually add them if you are not using a preprocessor like [`sass`](#sass), [`scss`](#scss) or [`less`](#less).\n\n   ```css\n   // these styles will apply when the component gets rendered on a 'dark' wrapper\n   // (supports 3 levels of nesting)\n   :global(.sthemer-dark) button,\n   :global(.sthemer-light) :global(.sthemer-dark) button,\n   :global(.sthemer-dark) :global(.sthemer-light) :global(.sthemer-dark) button {\n      background-color: white;\n      color: black;\n   }\n\n   // these styles will apply when the component gets rendered on a 'light' wrapper\n   // (supports 3 levels of nesting)\n   :global(.sthemer-light) button,\n   :global(.sthemer-dark) :global(.sthemer-light) button,\n   :global(.sthemer-light) :global(.sthemer-dark) :global(.sthemer-light) button {\n      background-color: black;\n      color: white;\n   }\n   ```\n\n### Server-Side Rendering (SSR)\n\n`sthemer` also works with your SvelteKit projects that perform server-side rendering.\n\n\u003e Note: this currently is only supported in Chrome \u003e= 93. The feature was not added yet for [Firefox](https://github.com/mozilla/standards-positions/issues/526) and [Safari](https://lists.webkit.org/pipermail/webkit-dev/2021-May/031856.html).\n\nIf you want to use the **[`inverted`](#inverted)** strategy at the root level or the **[`auto`](#auto)** strategy, you need to make a small adjustments to your SvelteKit project.\n\nBy default the server doesn't know what color scheme the user is using. To get that information the server has to respond with some custom HTTP headers. The browser then performs the request again with the information about the preferred color scheme.\n\n`sthemer` already provides this functionality and you just have to connect it to your SvelteKit project:\n\n-  if you don't have a _[hooks.server.js](https://kit.svelte.dev/docs/hooks#server-hooks)_ file yet, create one with the following content:\n\n   ```ts\n   export { handle } from 'sthemer/hooks'\n   ```\n\n-  or if you already have a _[hooks.server.js](https://kit.svelte.dev/docs/hooks#server-hooks)_ file, add following lines to the top of the `handle` function:\n\n   ```diff\n   +import { setupSthemer } from 'sthemer/hooks'\n\n   /** @type {import('@sveltejs/kit').Handle} */\n   export async function handle({ event, resolve }) {\n   +   const sthemerResponse = setupSthemer(event)\n   +   if (sthemerResponse) return sthemerResponse\n\n      // your custom logic goes here\n\n      return await resolve(event)\n   }\n   ```\n\n## Sponsors\n\n[Become a sponsor :heart:](https://github.com/sponsors/ivanhofer) if you want to support my open source contributions.\n\n\u003cp align=\"center\"\u003e\n   \u003ca href=\"https://cdn.jsdelivr.net/gh/ivanhofer/sponsors/sponsorkit/sponsors.svg\" title=\"ivanhofer's sponsors\"\u003e\n      \u003cimg src=\"https://cdn.jsdelivr.net/gh/ivanhofer/sponsors/sponsorkit/sponsors.svg\" alt=\"ivanhofer's sponsors\" /\u003e\n   \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n   Thanks for sponsoring my open source work!\n\u003c/p\u003e\n\n## FAQs\n\n### Do I need this library?\n\nProbably not, but `sthemer` offers everything you could potentially need and can handle edge-cases like [`SSR`](#server-side-rendering-ssr) and [nesting](#nested-schemes) for you.\nYou can also just take a look at the source code and take the parts you are interested in without using the full library.\n\n### Why should I specify the variants in each component instead of just replacing the color variables?\n\nInstead of writing your components like this:\n\n```scss\n:root {\n   --c-dark-primary: #212121;\n   --c-dark-secondary: #303030;\n   --c-dark-tertiary: #424242;\n   --c-light-primary: #fafafa;\n   --c-light-secondary: #f5f5f5;\n   --c-light-tertiary: #e0e0e0;\n}\n\nbutton {\n   color: var(--c);\n   background-color: var(--c-bg);\n   border-color: var(--c-border);\n\n   \u0026:hover {\n      background-color: var(--c-bg--hover);\n   }\n\n   \u0026.on-dark {\n      --c: var(--c-dark-primary);\n      --c-bg: var(--c-light-secondary);\n      --c-border: var(--c-light-tertiary);\n\n      \u0026:hover {\n         --c-bg--hover: var(--c-light-primary);\n      }\n   }\n\n   \u0026.on-light {\n      --c: var(--c-light-primary);\n      --c-bg: var(--c-dark-secondary);\n      --c-border: var(--c-dark-tertiary);\n\n      \u0026:hover {\n         --c-bg--hover: var(--c-dark-primary);\n      }\n   }\n}\n```\n\nyou could also use the following approach:\n\n```scss\n.dark-mode {\n   --c-primary: #212121;\n   --c-secondary: #303030;\n   --c-tertiary: #424242;\n   --c-primary-inverted: #fafafa;\n   --c-secondary-inverted: #f5f5f5;\n   --c-tertiary-inverted: #e0e0e0;\n}\n\n.light-mode {\n   --c-primary: #fafafa;\n   --c-secondary: #f5f5f5;\n   --c-tertiary: #e0e0e0;\n   --c-primary-inverted: #212121;\n   --c-secondary-inverted: #303030;\n   --c-tertiary-inverted: #424242;\n}\n\nbutton {\n   color: var(--c-primary);\n   background-color: var(--c-secondary-inverted);\n   border-color: var(--c-tertiary-inverted);\n\n   \u0026:hover {\n      background-color: var(--c-primary-inverted);\n   }\n}\n```\n\nBut you probably shouldn't do that. There are a few reasons why you should use the approach that `sthemer` provides:\n\n1. It makes it easier to reason about how the component changes its appearance based on the color scheme.\\\n   At first glance this may sound not so important. And it requires you to also write more code. But in the long run you will benefit from it. Not having to jump around different files to make a color adjustment will make your code more readable and easier to maintain.\n\n2. You probably need to vary from the variables you define at the root in some edge cases which will become hard to implement when you base your theming on pre-existing variables.\\\n   The border color on the dark button looks a bit odd. Maybe we should try to use `--c-primary-inverted`, but we want to keep `--c-tertiary-inverted` for the light button. How would you do that? You would probably need to define a new variable called `--c-button-border` and a few weeks later you will have 20+ variables defined at the root level, that are decoupled from their components and only get used a single time for a specific component.\n\n### Why do I have to manually specify multiple levels of nesting?\n\nNesting is a feature that probably won't get used by most projects. Nesting produces more code, so it is disabled per default (see also next question). If you want to use it, [you can enable it](#nested-schemes) easily.\n\n### Why is the generated CSS so big when using multiple levels of nesting?\n\nThe more levels of nesting you are using, the longer the required CSS selector is and so the file size of the resulting CSS will be larger. But It will not account that much to the amount of data an user has to download. Modern browsers and tools have good support for [gzip](https://www.gnu.org/software/gzip/) compression. Because those selectors will look quite similar, gzip compression will perform great and reduce the size.\n\n### Why does the scheme class not get applied to the HTML tag?\n\nYou probably will never need it. You can wrap the root of your application with the `Sthemer` and define your styles there. This approach also makes it possible to use [nested schemes](#nested-schemes) in a consistent way.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivanhofer%2Fsthemer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fivanhofer%2Fsthemer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivanhofer%2Fsthemer/lists"}