search
HomeWeb Front-endCSS TutorialCSS Theme Selector with Automatic Mode [Tutorial]

This tutorial shows you how to create a theme selector in Svelte, enabling multiple theme options for your website. It also includes an automatic theme that adapts to the user's device settings.

Although it's implemented in Svelte, most of the functionality relies on standard HTML and CSS, making it easy to replicate in other frameworks.

The source code for the project can be found here: https://github.com/ivarnm/theme_selector

Live demo: https://theme-selector-inm.vercel.app/

Setting up the project:

If you don't already have an existing Svelte project, you can create one by following Svelte's getting started guide: https://svelte.dev/docs/introduction

Add color variables:

We will use CSS variables to define our color themes. First, create a src/styles/global.css file and add the following CSS:

:root {
  --neutral-0: white;
  --neutral-10: #fffcf1;
  --neutral-30: #d2d2d2;
  --neutral-40: #b7b7b7;
  --neutral-60: #666666;
  --neutral-70: #333333;
  --neutral-100: black;

  --primary-60: #696d90;
  --primary-70: #3D405B;
  --primary-80: #303349;

  --secondary-70: #5a7b6b;
  --secondary-80: #456153;
}

.dark-theme {
  --neutral-0: black;
  --neutral-10: #1a1a1a;
  --neutral-30: #3d3d3d;
  --neutral-40: #595959;
  --neutral-60: #999999;
  --neutral-70: #cccccc;
  --neutral-100: white;

  --primary-60: #979ec7;
  --primary-70: #a7aed6;
  --primary-80: #b8bfe9;

  --secondary-70: #79BEA5;
  --secondary-80: #89cfb5;
}

.warm-theme {
  --neutral-0: #fff7e0;
  --neutral-10: #ffedcc;
  --neutral-30: #ffdbb7;
  --neutral-40: #ffb89d;
  --neutral-60: #ff9473;
  --neutral-70: #ff5733;
  --neutral-100: #4d2600;

  --primary-60: #f28e2b;
  --primary-70: #d65a31;
  --primary-80: #c44536;

  --secondary-70: #e59572;
  --secondary-80: #cf6448;
}

html {
  font-family: 'Inter', sans-serif;
  background-color: var(--neutral-10);
}

body {
  margin: 0 auto;
  max-width: 870px;
  color: var(--neutral-70);
}

This defines a color palette for the light theme on the root element and adds a dark and warm theme. Which colors and themes you want is, of course, up to you. These are just some examples. You also see how we can use these variables to set the page's background color and the body's color property.

Next, add a src/routes/ layout.svelte file and add the following code to import the CSS file globally:

<script>
  import "../styles/global.css";
</script>

Create a dropdown menu:

I prefer to have my theme selector inside a dropdown menu, so I'll make a reusable DropdownMenu component that we can use. If you want to do the same, you can create a src/lib/components/DropdownMenu.svelte file and add the following:

<script lang="ts">
  let menuOpen = false;

  const toggleMenu = () => {
    menuOpen = !menuOpen;
  };

  const handleDropdownFocusLoss = (event: FocusEvent) => {
    const focusedElement = event.relatedTarget instanceof HTMLElement ? event.relatedTarget : null;
    const menuElement = event.currentTarget instanceof HTMLElement ? event.currentTarget : null;

    // Check if the new focus is inside the menu
    if (focusedElement && menuElement && menuElement.contains(focusedElement)) {
        return;
    }

    menuOpen = false;
  };
</script>

<div class="container">
  <div class="menu" on:focusout="{handleDropdownFocusLoss}">
    <button class="icon-btn" on:click="{toggleMenu}">
      <slot name="icon"></slot>
    </button>

    {#if menuOpen}
      <div class="dropdown">
        <slot name="dropdown"></slot>
      </div>
    {/if}
  </div>
</div>

<style>
  .menu {
    position: relative;
  }

  .icon-btn {
    background-color: transparent;
    border: none;
    cursor: pointer;
    padding: 6px;
    margin: 0;
    border-radius: 50%;
    overflow: hidden;
    width: 49px;
    height: 49px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .icon-btn:hover {
    outline: none;
    background-color: var(--neutral-30);
  }

  .dropdown {
    position: absolute;
    top: 60px;
    right: 0;
    background-color: var(--neutral-30);
    border: 1px solid var(--neutral-40);
    color: var(--neutral-100);
    padding: 5px;
    z-index: 100;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: 200px;
  }

  .dropdown :global(button) {
    padding: 10px 15px;
    font-size: 20px;
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
  }

  .dropdown :global(button:hover) {
    background-color: var(--neutral-40);
  }
</style>

Create the theme selector component:

Now it's time to create the component that will handle the theme selection. Create the file
src/lib/components/ThemeSelector.svelte and add the following:

<script lang="ts">
  import { onMount } from 'svelte';
  import { writable } from 'svelte/store';
  import DropdownMenu from '$lib/components/DropdownMenu.svelte';

  // If you want to use the theme variable in other components, you can move it to a dedicated ts/js file and import it here instead
  let theme = writable<string>("system");

  // Define your themes and their names.
  const THEMES = [
      { value: 'system', label: 'Automatic' },
      { value: 'light', label: 'Light' },
      { value: 'dark', label: 'Dark' },
      { value: 'warm', label: 'Warm' },
  ];

  onMount(() => {
    // Prevents the code from running on the server
    if (typeof window == 'undefined') return;

    let storedTheme = localStorage.getItem('theme');

    // Get the user's system theme preference
    let systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 

    if (storedTheme && THEMES.some(themeOption => themeOption.value === storedTheme)) {
      theme.set(storedTheme);
    } 
    else {
      theme.set('system');
      applyTheme(systemTheme);
    }

    // Update the automatic theme when the system theme changes if the theme is set to automatic
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
      systemTheme = e.matches ? 'dark' : 'light';
      if (storedTheme === 'system') {
        applyTheme(systemTheme);
      }
    });

    theme.subscribe(value => {
      if (value === 'system') {
        applyTheme(systemTheme);
      } 
      else {
        applyTheme(value);
      }

      localStorage.setItem('theme', value);
      storedTheme = value;
    });
  });

  function applyTheme(theme: string) {
    document.documentElement.className = "";
    if(theme === 'system') return;

    // Add the theme class (e.g dark-theme) to the document element to apply the theme
    document.documentElement.classList.add(`${theme}-theme`);
  }

</script>

<dropdownmenu>
  <svg slot="icon" width="37" height="37" viewbox="0 0 37 37" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M29 18.5C29 24.299 24.299 29 18.5 29C12.701 29 8 24.299 8 18.5C8 12.701 12.701 8 18.5 8C24.299 8 29 12.701 29 18.5Z" stroke="currentColor" stroke-width="2"></path>
    <path d="M9 28L6.17157 30.8284" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
    <path d="M1 18.5H5" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
    <path d="M9 9L6.17157 6.17157" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
    <path d="M18.5 5L18.5 1" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
    <path d="M28 9L30.8284 6.17157" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
    <path d="M32 18.5H36" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
    <path d="M28 28L30.8284 30.8284" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
    <path d="M18.5 36L18.5 32" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
    <circle cx="14.5" cy="18.5" r="6.5" stroke="currentColor" stroke-width="2"></circle>
    <line x1="8.03459" y1="22.0512" x2="17.227" y2="12.8588" stroke="currentColor" stroke-width="2"></line>
    <line x1="11.0346" y1="25.0512" x2="20.227" y2="15.8588" stroke="currentColor" stroke-width="2"></line>
  </svg>  

  <div slot="dropdown">
    {#each THEMES as themeOption}
      <button class="theme-button" class:selected="{$theme" themeoption.value on:click="{()"> $theme = themeOption.value}
      >
        <svg width="24px" height="24px" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
          <circle cx="12" cy="12" r="7" fill="{$theme === themeOption.value ? 'var(--primary-60)' : 'transparent'}"></circle>
        </svg>
        <span>{themeOption.label}</span>
      </button>
    {/each}
  </div>
</dropdownmenu>

<style>
  :global(svg) {
    color: var(--neutral-70);
  }

  .theme-button.selected {
    font-weight: bold;
  }

  button {
    margin: 0;
    padding: 0;
    border: none;
    border-radius: 4px;
    background-color: transparent;
    color: inherit;
    cursor: pointer;
  }
</style>

When the component is loaded in the browser, we check to see if the user has previously selected a theme. If not it defaults to the automatic theme. The automatic theme sets the theme to either light or dark using the media query prefers-color-scheme to detect if the user has requested light or dark color themes in their OS.

When setting a theme, for instance warm, the class warm-theme is added to the root element of the page, which will override the CSS color variables to the ones we previously defined in the .warm-theme selector in our global.css file.

Adding the theme selector:

Now we can add the ThemeSelector component to our layout file. Change the content of the src/routes/ layout.svelte file to this:

<script>
  import '../styles/global.css';
  import ThemeSelector from '$lib/components/ThemeSelector.svelte';
</script>

<div>
  <header>
    <themeselector></themeselector>
  </header>
  <main>
    <slot></slot>
  </main>
</div>

<style>
  header {
    display: flex;
    justify-content: flex-end;
  }

  main {
    margin: 50px 0;
  }
</style>

This will add the theme selector dropdown to the top right of all pages.

Add some example content to view the themes:

In the src/routes/ page.svelte file we can add some boxes to view the color themes:

<h1 id="Theme-selector">Theme selector</h1>

<div class="box-container">
  <div class="box neutral-0">--neutral-0</div>
  <div class="box neutral-10">--neutral-10</div>
  <div class="box neutral-30">--neutral-30</div>
  <div class="box neutral-40">--neutral-40</div>
  <div class="box neutral-60">--neutral-60</div>
  <div class="box neutral-70">--neutral-70</div>
  <div class="box neutral-100">--neutral-100</div>
  <div class="box primary-60">--primary-60</div>
  <div class="box primary-70">--primary-70</div>
  <div class="box primary-80">--primary-80</div>
  <div class="box secondary-70">--secondary-70</div>
  <div class="box secondary-80">--secondary-80</div>
</div>

<style>
  .box-container {
    display: flex;
    gap: 30px;
    flex-wrap: wrap;
  }

  .box {
    width: 150px;
    height: 150px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .neutral-0 {
    background-color: var(--neutral-0);
    color: var(--neutral-100);
  }

  .neutral-10 {
    background-color: var(--neutral-10);
    color: var(--neutral-100);
    border: 5px solid var(--neutral-30);
    box-sizing: border-box;
  }

  .neutral-30 {
    background-color: var(--neutral-30);
    color: var(--neutral-100);
  }

  .neutral-40 {
    background-color: var(--neutral-40);
    color: var(--neutral-100);
  }

  .neutral-60 {
    background-color: var(--neutral-60);
    color: var(--neutral-0);
  }

  .neutral-70 {
    background-color: var(--neutral-70);
    color: var(--neutral-0);
  }

  .neutral-100 {
    background-color: var(--neutral-100);
    color: var(--neutral-0);
  }

  .primary-60 {
    background-color: var(--primary-60);
    color: var(--neutral-0);
  }

  .primary-70 {
    background-color: var(--primary-70);
    color: var(--neutral-0);
  }

  .primary-80 {
    background-color: var(--primary-80);
    color: var(--neutral-0);
  }

  .secondary-70 {
    background-color: var(--secondary-70);
    color: var(--neutral-0);
  }

  .secondary-80 {
    background-color: var(--secondary-80);
    color: var(--neutral-0);
  }
</style>

And here is the final result

CSS Theme Selector with Automatic Mode [Tutorial]

The above is the detailed content of CSS Theme Selector with Automatic Mode [Tutorial]. For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Static First: Pre-Generated JAMstack Sites with Serverless Rendering as a FallbackStatic First: Pre-Generated JAMstack Sites with Serverless Rendering as a FallbackApr 16, 2025 am 11:06 AM

You might be seeing the term JAMstack popping up more and more frequently. I’ve been a fan of it as an approach for some time.

CSS-Tricks Chronicle XXXVICSS-Tricks Chronicle XXXVIApr 16, 2025 am 10:58 AM

This is one of these little roundups of things going on with myself, this site, and the other sites that are part of the CSS-Tricks family.

Weekly Platform News: Emoji String Length, Issues with Rounded Buttons, Bundled ExchangesWeekly Platform News: Emoji String Length, Issues with Rounded Buttons, Bundled ExchangesApr 16, 2025 am 10:46 AM

In this week's roundup, the string length of two emojis is not always equal, something to consider before making that rounded button, and we may have a new

Meeting GraphQL at a Cocktail MixerMeeting GraphQL at a Cocktail MixerApr 16, 2025 am 10:43 AM

GraphQL and REST are two specifications used when building APIs for websites to use. REST defines a series of unique identifiers (URLs) that applications use

Introducing Sass ModulesIntroducing Sass ModulesApr 16, 2025 am 10:42 AM

Sass just launched a major new feature you might recognize from other languages: a module system. This is a big step forward for @import. one of the most-used

How I Learned to Stop Worrying and Love Git HooksHow I Learned to Stop Worrying and Love Git HooksApr 16, 2025 am 10:41 AM

The merits of Git as a version control system are difficult to contest, but while Git will do a superb job in keeping track of the commits you and your

A Proof of Concept for Making Sass FasterA Proof of Concept for Making Sass FasterApr 16, 2025 am 10:38 AM

At the start of a new project, Sass compilation happens in the blink of an eye. This feels great, especially when it’s paired with Browsersync, which reloads

Demonstrating Reusable React Components in a FormDemonstrating Reusable React Components in a FormApr 16, 2025 am 10:36 AM

Components are the building blocks of React applications. It’s almost impossible to build a React application and not make use of components. It’s widespread

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Chat Commands and How to Use Them
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Dreamweaver Mac version

Dreamweaver Mac version

Visual web development tools

PhpStorm Mac version

PhpStorm Mac version

The latest (2018.2.1) professional PHP integrated development tool

SublimeText3 English version

SublimeText3 English version

Recommended: Win version, supports code prompts!

DVWA

DVWA

Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

mPDF

mPDF

mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),