[READ-ONLY] Mirror of https://github.com/flo-bit/ui-kit. 🦊 fox ui, svelte 5 and tailwind 4 flo-bit.dev/ui-kit/
svelte tailwindcss ui-components
0

Configure Feed

Select the types of activity you want to include in your feed.

add theme toggle

Florian (Oct 15, 2025, 9:02 PM +0200) ecd32216 2c550399

+148 -94
+5
.changeset/thick-ears-know.md
··· 1 + --- 2 + '@foxui/core': patch 3 + --- 4 + 5 + add themetoggle, use modewatcher
-30
apps/docs/src/app.html
··· 12 12 data-website-id="e6166872-1b90-4b72-8f63-683b95fc010f" 13 13 data-domains="flo-bit.dev" 14 14 ></script> 15 - 16 - <script> 17 - let dark = localStorage.getItem('darkMode'); 18 - if (dark) { 19 - dark = JSON.parse(dark); 20 - } else { 21 - dark = window.matchMedia('(prefers-color-scheme: dark)').matches; 22 - } 23 - 24 - var root = document.getElementsByTagName('html')[0]; 25 - if (dark) { 26 - root.classList.add('dark'); 27 - } else { 28 - root.classList.remove('dark'); 29 - } 30 - 31 - // get accent color from local storage 32 - let accentColor = localStorage.getItem('accentColor'); 33 - if (accentColor) { 34 - accentColor = JSON.parse(accentColor); 35 - root.classList.add(accentColor); 36 - } 37 - 38 - // get base color from local storage 39 - let baseColor = localStorage.getItem('baseColor'); 40 - if (baseColor) { 41 - baseColor = JSON.parse(baseColor); 42 - root.classList.add(baseColor); 43 - } 44 - </script> 45 15 </head> 46 16 <body 47 17 data-sveltekit-preload-data="hover"
+35
apps/docs/src/lib/cards/base/CardThemeToggle.svelte
··· 1 + <script> 2 + import { cn, toggleVariants } from '@foxui/all'; 3 + </script> 4 + 5 + <div class="flex gap-4"> 6 + <svg 7 + xmlns="http://www.w3.org/2000/svg" 8 + fill="none" 9 + viewBox="0 0 24 24" 10 + stroke-width="1.5" 11 + stroke="currentColor" 12 + class="block size-5 transition-colors duration-500" 13 + > 14 + <path 15 + stroke-linecap="round" 16 + stroke-linejoin="round" 17 + d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" 18 + /> 19 + </svg> 20 + 21 + <svg 22 + xmlns="http://www.w3.org/2000/svg" 23 + fill="none" 24 + viewBox="0 0 24 24" 25 + stroke-width="1.5" 26 + stroke="currentColor" 27 + class="size-5 transition-colors duration-500 dark:block dark:text-white" 28 + > 29 + <path 30 + stroke-linecap="round" 31 + stroke-linejoin="round" 32 + d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" 33 + /> 34 + </svg> 35 + </div>
+6
apps/docs/src/lib/site-components/components_base.ts
··· 20 20 import CardProse from '$lib/cards/base/CardProse.svelte'; 21 21 import CardAccordion from '$lib/cards/base/CardAccordion.svelte'; 22 22 import CardSelect from '$lib/cards/base/CardSelect.svelte'; 23 + import CardThemeToggle from '$lib/cards/base/CardThemeToggle.svelte'; 23 24 24 25 import type { Component } from 'svelte'; 25 26 import CardPopover from '$lib/cards/base/CardPopover.svelte'; ··· 172 173 component: CardToggleGroup, 173 174 label: 'Toggle Group', 174 175 href: 'toggle-group' 176 + }, 177 + { 178 + component: CardThemeToggle, 179 + label: 'Theme Toggle', 180 + href: 'theme-toggle' 175 181 } 176 182 ].sort((a, b) => a.label.localeCompare(b.label));
+8
apps/docs/src/routes/(main)/components/core/theme-toggle/+page.svelte
··· 1 + <script lang="ts"> 2 + import ThemeToggleDocs from './ThemeToggle.md'; 3 + import { Prose } from '@foxui/all'; 4 + </script> 5 + 6 + <Prose> 7 + <ThemeToggleDocs /> 8 + </Prose>
+7
apps/docs/src/routes/(main)/components/core/theme-toggle/Example.svelte
··· 1 + <script> 2 + import { ThemeToggle } from '@foxui/all'; 3 + </script> 4 + 5 + <div class="flex items-center gap-2"> 6 + <ThemeToggle /> 7 + </div>
+29
apps/docs/src/routes/(main)/components/core/theme-toggle/ThemeToggle.md
··· 1 + <script> 2 + import Example from './Example.svelte'; 3 + </script> 4 + 5 + # Theme Toggle 6 + 7 + ## Example 8 + 9 + <Example /> 10 + 11 + ## Usage 12 + 13 + ```svelte 14 + <script> 15 + import { ThemeToggle } from '@foxui/core'; 16 + </script> 17 + 18 + <ThemeToggle /> 19 + ``` 20 + 21 + If this component is used anywhere on your site, the mode will default to system settings (if the user has not manually set the mode). 22 + 23 + ## Accessibility 24 + 25 + - If javascript is disabled, the theme toggle will not be shown. 26 + 27 + ## Credits 28 + 29 + Based on [mode-watcher](https://github.com/svecosystem/mode-watcher), also exports all functions and stores from the package.
+12 -11
apps/docs/src/routes/(main)/docs/quick-start/QuickStart.md
··· 14 14 15 15 Also add the `@tailwindcss/typography` and `@tailwindcss/forms` plugins. 16 16 17 - ## 2. Install all fox ui components by running: 17 + ## 2. Install the core fox ui components by running: 18 18 19 19 ```bash 20 - npm install @foxui/all 20 + npm install @foxui/core 21 21 ``` 22 22 23 23 ## 3. Set theme variables in your `app.css` ··· 26 26 27 27 ```css 28 28 @source "../node_modules/@foxui"; 29 + 30 + @custom-variant dark (&:is(.dark *)); 29 31 30 32 @theme { 31 33 --color-base-50: var(--color-zinc-50); ··· 58 60 59 61 ```svelte 60 62 <script> 61 - import { Button } from '@foxui/all'; 63 + import { Button } from '@foxui/core'; 62 64 </script> 63 65 64 66 <Button>Click me</Button> ··· 72 74 Simply install a package when you need it, or you can install all of them with: 73 75 74 76 ```bash 75 - npm install @foxui/all 77 + npm install @foxui/core @foxui/3d @foxui/colors @foxui/social @foxui/text @foxui/time @foxui/visual 76 78 ``` 77 79 78 80 ### Dark mode 79 81 80 - This ui kit is designed to be used in both light and dark mode 81 - (and switch automatically depending on system settings). 82 - If you want to disable dark mode, add the following to your app.css: 82 + This ui kit is designed to be used in both light and dark mode, and can be used in the following ways: 83 + 84 + 1. light mode only (default), for dark mode only, add the `dark` class to the `html` element in your app. 85 + 86 + 2. Allow users to switch between modes using the [\<ThemeToggle /\>](/ui-kit/components/core/theme-toggle) component (before being pressed will default mode to system settings). 83 87 88 + 3. automatically chooses the mode based on the system settings, simply remove the following from your app.css: 84 89 ```css 85 90 @custom-variant dark (&:is(.dark *)); 86 91 ``` 87 92 88 - Similarly you can disable light mode: 89 - 90 - 1. Add the above code to your app.css 91 - 2. Add the `dark` class to the `html` element in your app.
+4 -3
packages/core/package.json
··· 46 46 "@eslint/compat": "^1.2.5", 47 47 "@eslint/js": "^9.18.0", 48 48 "@sveltejs/adapter-auto": "^6.0.0", 49 + "@sveltejs/adapter-static": "^3.0.8", 49 50 "@sveltejs/kit": "^2.16.0", 51 + "@sveltejs/package": "^2.3.11", 50 52 "@sveltejs/vite-plugin-svelte": "^5.0.0", 51 53 "@tailwindcss/forms": "^0.5.9", 52 54 "@tailwindcss/typography": "^0.5.15", ··· 63 65 "tailwindcss": "^4.1.5", 64 66 "typescript": "^5.0.0", 65 67 "typescript-eslint": "^8.20.0", 66 - "vite": "^6.2.6", 67 - "@sveltejs/adapter-static": "^3.0.8", 68 - "@sveltejs/package": "^2.3.11" 68 + "vite": "^6.2.6" 69 69 }, 70 70 "dependencies": { 71 71 "@number-flow/svelte": "^0.3.7", 72 72 "bits-ui": "^1.4.3", 73 73 "clsx": "^2.1.1", 74 + "mode-watcher": "^1.1.0", 74 75 "svelte-sonner": "^0.3.28", 75 76 "tailwind-merge": "^3.2.0", 76 77 "tailwind-variants": "^1.0.0"
+12 -49
packages/core/src/lib/components/theme-toggle/ThemeToggle.svelte
··· 1 - <script lang="ts" module> 2 - export const theme = $state({ 3 - dark: false 4 - }); 5 - </script> 6 - 7 1 <script lang="ts"> 8 2 import { onMount } from 'svelte'; 9 3 import { Button, type ButtonProps } from '../button'; 10 4 import { cn } from '../../utils'; 5 + import { ModeWatcher, toggleMode } from 'mode-watcher'; 11 6 12 - onMount(() => { 13 - // load from local storage 14 - const savedDarkMode = localStorage.getItem('darkMode'); 15 - if (savedDarkMode) { 16 - theme.dark = JSON.parse(savedDarkMode); 17 - } else { 18 - // prefers color scheme? 19 - theme.dark = window.matchMedia('(prefers-color-scheme: dark)').matches; 20 - } 7 + let { 8 + class: className, 9 + ref = $bindable(null), 10 + defaultMode = 'system', 11 + ...restProps 12 + }: ButtonProps & { 13 + defaultMode?: 'light' | 'dark' | 'system'; 14 + } = $props(); 15 + </script> 21 16 22 - // remove local storage 23 - // localStorage.removeItem("darkMode"); 24 - setTheme(theme.dark); 25 - 26 - // recommended method for newer browsers: specify event-type as first argument 27 - window 28 - .matchMedia('(prefers-color-scheme: dark)') 29 - .addEventListener('change', (e) => e.matches && toggleTheme()); 30 - 31 - window 32 - .matchMedia('(prefers-color-scheme: light)') 33 - .addEventListener('change', (e) => e.matches && toggleTheme()); 34 - }); 35 - 36 - function setTheme(dark: Boolean) { 37 - var root = document.getElementsByTagName('html')[0]; 38 - 39 - if (dark) { 40 - root.classList.add('dark'); 41 - } else { 42 - root.classList.remove('dark'); 43 - } 44 - } 45 - 46 - function toggleTheme() { 47 - theme.dark = !theme.dark; 48 - // save to local storage 49 - localStorage.setItem('darkMode', JSON.stringify(theme.dark)); 50 - setTheme(theme.dark); 51 - } 52 - 53 - let { class: className, ref = $bindable(null), ...restProps }: ButtonProps = $props(); 54 - </script> 17 + <ModeWatcher {defaultMode} /> 55 18 56 19 <Button 57 20 variant="link" 58 - onclick={toggleTheme} 21 + onclick={toggleMode} 59 22 class={cn( 60 23 'theme-toggle focus-visible:outline-base-900 dark:focus-visible:outline-base-100 flex items-center justify-center rounded-2xl focus-visible:outline-2', 61 24 className
+8 -1
packages/core/src/lib/components/theme-toggle/index.ts
··· 1 1 export { default as ThemeToggle } from './ThemeToggle.svelte'; 2 - export { theme } from './ThemeToggle.svelte'; 2 + export { 3 + toggleMode, 4 + setMode, 5 + resetMode, 6 + mode, 7 + userPrefersMode, 8 + systemPrefersMode 9 + } from 'mode-watcher';
+22
pnpm-lock.yaml
··· 414 414 clsx: 415 415 specifier: ^2.1.1 416 416 version: 2.1.1 417 + mode-watcher: 418 + specifier: ^1.1.0 419 + version: 1.1.0(svelte@5.36.16) 417 420 svelte-sonner: 418 421 specifier: ^0.3.28 419 422 version: 0.3.28(svelte@5.36.16) ··· 4224 4227 resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==, tarball: https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz} 4225 4228 dev: false 4226 4229 4230 + /mode-watcher@1.1.0(svelte@5.36.16): 4231 + resolution: {integrity: sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g==, tarball: https://registry.npmjs.org/mode-watcher/-/mode-watcher-1.1.0.tgz} 4232 + peerDependencies: 4233 + svelte: ^5.27.0 4234 + dependencies: 4235 + runed: 0.25.0(svelte@5.36.16) 4236 + svelte: 5.36.16 4237 + svelte-toolbelt: 0.7.1(svelte@5.36.16) 4238 + dev: false 4239 + 4227 4240 /mri@1.2.0: 4228 4241 resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} 4229 4242 engines: {node: '>=4'} ··· 4902 4915 4903 4916 /runed@0.23.4(svelte@5.36.16): 4904 4917 resolution: {integrity: sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==, tarball: https://registry.npmjs.org/runed/-/runed-0.23.4.tgz} 4918 + peerDependencies: 4919 + svelte: ^5.7.0 4920 + dependencies: 4921 + esm-env: 1.2.2 4922 + svelte: 5.36.16 4923 + dev: false 4924 + 4925 + /runed@0.25.0(svelte@5.36.16): 4926 + resolution: {integrity: sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg==, tarball: https://registry.npmjs.org/runed/-/runed-0.25.0.tgz} 4905 4927 peerDependencies: 4906 4928 svelte: ^5.7.0 4907 4929 dependencies: