Skip to main content
The ThemeButton component provides an elegant theme switching interface with animated icon transitions. It integrates with a theme management system to toggle between light and dark modes while persisting user preferences.

Features

  • Theme Toggle: Switches between light and dark themes
  • Animated Transitions: Smooth opacity fade when changing icons
  • Persistent Storage: Remembers user theme preference
  • Customizable Position: Flexible className prop for positioning
  • Hover Effects: Scale animation on hover
  • High Priority Loading: Eager loading to prevent theme flash
  • SVG Icons: Optimized vector graphics for crisp display

Usage

Basic Implementation

---
import ThemeButton from '@/components/ThemeButton.astro';
---

<ThemeButton />

Custom Positioning

---
import ThemeButton from '@/components/ThemeButton.astro';
---

<!-- Top left corner -->
<ThemeButton className="fixed top-3 left-3 rounded p-2" />

<!-- Bottom right corner -->
<ThemeButton className="fixed bottom-5 right-5 rounded-full p-3" />

<!-- Inline with navigation -->
<nav>
  <ThemeButton className="relative inline-block" />
</nav>

Props

className
string
default:"fixed top-3 right-3 cursor-pointer rounded p-2"
Custom CSS classes for positioning and styling the theme button. The default positions the button in the top-right corner of the viewport.

Component Structure

HTML Output

<button 
  id="theme-toggle" 
  class="fixed top-3 right-3 cursor-pointer rounded p-2 transition duration-300 ease-in-out hover:scale-120 z-9999"
>
  <img 
    id="theme-toggle-icon"
    alt="Dark Icon"
    src="/path/to/icon.svg"
    class="h-[2vw] max-h-6 min-h-5 w-auto transition-opacity duration-500 ease-in-out"
    loading="eager"
  />
</button>

Styling

Button Classes

The button includes built-in transition and hover effects:
transition duration-300 ease-in-out
hover:scale-120
z-9999

Icon Sizing

The icon uses responsive sizing with constraints:
/* Responsive size based on viewport */
h-[2vw]

/* Maximum size cap */
max-h-6

/* Minimum size floor */
min-h-5

/* Maintain aspect ratio */
w-auto

Icon Transition

transition-opacity duration-500 ease-in-out
The 500ms opacity transition creates a smooth fade effect when switching theme icons.

Theme Management

Theme System Integration

The component integrates with a centralized theme management system:
import { THEME_ICONS, getCurrentTheme, updateTheme } from "@/theme/theme"

Functions Used

getCurrentTheme()
() => 'light' | 'dark'
Returns the current active theme from storage or system preference.
updateTheme(theme)
(theme: string) => void
Updates the theme in storage and applies it to the document.
THEME_ICONS
Record<string, ImageMetadata>
Object containing icon assets for each theme.

Behavior

Initialization

On component mount, the button displays the correct icon for the current theme:
const icon = document.getElementById("theme-toggle-icon");
icon.src = THEME_ICONS[getCurrentTheme()].src;

Theme Toggle Animation

When clicked, the button performs a smooth transition:
toggleButton.addEventListener("click", () => {
  // 1. Fade out current icon
  icon.style.opacity = "0";

  // 2. Determine new theme
  const currentTheme = getCurrentTheme() === "dark" ? "light" : "dark";
  
  // 3. Update theme in system
  updateTheme(currentTheme);

  // 4. After 200ms, swap icon and fade in
  setTimeout(() => {
    icon.src = THEME_ICONS[currentTheme].src;
    icon.alt = currentTheme + " theme icon";
    icon.style.opacity = "1";
  }, 200);
});

Animation Timeline

1

Fade Out (0-200ms)

Current icon opacity transitions to 0
2

Swap Icon (200ms)

Icon source changes to new theme icon
3

Fade In (200-700ms)

New icon opacity transitions to 1 over 500ms

Image Optimization

The component uses Astro’s Image component for optimization:
import { Image } from "astro:assets";

Image Props

loading
'eager'
Loads the image immediately to prevent theme flash on page load.
fetchpriority
'high'
Prioritizes loading of the theme icon for better user experience.
format
'svg'
Uses SVG format for crisp, scalable icons at any size.

Accessibility

Alt Text

The icon includes descriptive alt text that updates with the theme:
icon.alt = currentTheme + " theme icon";

Button Semantics

The button is properly labeled with:
  • Semantic <button> element
  • Unique id for JavaScript targeting
  • Descriptive icon alt text

Keyboard Support

As a native button element, it supports:
  • Enter key activation
  • Space key activation
  • Focus states

Z-Index Management

The button uses a high z-index to stay above other content:
z-9999
Ensure this z-index value doesn’t conflict with other fixed or sticky elements in your layout.

Positioning Examples

Fixed Positions

<ThemeButton className="fixed top-3 right-3 rounded p-2" />

Relative Positioning

<!-- Inside a header component -->
<header class="flex justify-between items-center p-4">
  <Logo />
  <nav>
    <NavLinks />
    <ThemeButton className="relative inline-block ml-4" />
  </nav>
</header>

Theme System Requirements

This component requires a theme management system at @/theme/theme with:

Required Exports

// Current theme getter
export function getCurrentTheme(): 'light' | 'dark';

// Theme updater
export function updateTheme(theme: 'light' | 'dark'): void;

// Icon mappings
export const THEME_ICONS: {
  light: ImageMetadata;
  dark: ImageMetadata;
};

Example Theme Module

// src/theme/theme.ts
import lightIcon from '@/assets/icons/light.svg';
import darkIcon from '@/assets/icons/dark.svg';

export const THEME_ICONS = {
  light: lightIcon,
  dark: darkIcon
};

export function getCurrentTheme() {
  return localStorage.getItem('theme') || 'light';
}

export function updateTheme(theme) {
  localStorage.setItem('theme', theme);
  document.documentElement.setAttribute('data-theme', theme);
}

Performance Considerations

Eager Loading

The icon is loaded with high priority to prevent:
  • Layout shift
  • Theme flash
  • Icon pop-in

SVG Format

Using SVG icons provides:
  • Small file size
  • Scalability without quality loss
  • Fast rendering
  • No pixelation at any size

Customization

Custom Hover Effects

<ThemeButton 
  className="fixed top-3 right-3 rounded-full p-3 hover:rotate-180 hover:scale-150 transition-all duration-500" 
/>

Custom Icon Sizes

<ThemeButton className="fixed top-3 right-3 p-2">
  <!-- Override icon size with CSS -->
</ThemeButton>

<style>
  #theme-toggle-icon {
    height: 32px;
    width: 32px;
  }
</style>

Rounded Variants

<!-- Circular button -->
<ThemeButton className="fixed top-3 right-3 rounded-full p-3" />

<!-- Square button -->
<ThemeButton className="fixed top-3 right-3 rounded-none p-2" />

<!-- Pill button -->
<ThemeButton className="fixed top-3 right-3 rounded-full px-4 py-2" />

Build docs developers (and LLMs) love