SecretoCho
menu

component 04 / 28WORKING

SC Theme Image Switcher

type
Code Component
platform
Framer
last update
license
SecretoCho Design
needs JavaScript

demo · runs with JavaScript

fig 00 · live demo, ported from SC_Image_swither.tsx

Auto-swap images for light/dark themes using system and/or Framer theme detection with live updates.

A theme-aware image component for Framer that automatically swaps between light and dark images. It can detect the current theme via system preference, Framer’s theme attributes, or both (recommended), and updates live on changes. Supports configurable size, object-fit, and corner radius, with graceful empty-state hints when an image is missing.

  • UI
  • Media
  • Utilities
  • Theming

Main

Overview

Built With
React, DOM APIs (matchMedia, MutationObserver), Framer Code Component API
Layout Support
Fixed width/height (via props)
Orientation
N/A
Ideal Use Case
Logos, illustrations, screenshots that must invert/swap per theme

Tags

  • theme
  • dark mode
  • image
  • matchMedia
  • mutation observer
  • framer

Code comment

/** * SC Theme Image Switcher * © SecretoCho Design 2025 (https://secretocho.design) * Swaps between light/dark images based on theme (system / Framer / both). * Listens to matchMedia and DOM attribute mutations for live updates. * @framerDisableUnlink */

Dependencies

Technologies
None (pure React + Web APIs)
Notes
Relies on browser support for prefers-color-scheme and MutationObserver.

Complexity

Logic Level
Low–Medium (multi-source theme detection + observers)
Runtime Cost
Very Low (no animations; simple state updates)
Component Size
~2–3 KB TSX
Notes
Cleans up media/observer listeners on unmount.

Guide

Instructions

  1. Drop the component onto the canvas.
  2. Provide Light Mode Image and Dark Mode Image.
  3. Choose Theme Detection: system, framer, or both (recommended).
  4. Adjust Width, Height, Image Fit, and Corner Radius.
  5. Preview theme switching by toggling your OS/Framer theme.

Examples / Presets

  • Preset A – Logo Swap

    detect
    both
    fit
    contain
    radius
    0
    width
    240
    height
    120
  • Preset B – Illustration Cover

    detect
    system
    fit
    cover
    radius
    16
    width
    600
    height
    400
  • Preset C – Framer Only

    detect
    framer
    fit
    scale-down
    radius
    12
    width
    320
    height
    240

Mechanics

How it Works

  • System detection: window.matchMedia("(prefers-color-scheme: dark)").
  • Framer detection: checks documentElement/body for data-theme, data-framer-theme, toggle-theme, and .dark class.
  • Both: first tries Framer attributes; if not dark, falls back to system preference.
  • Subscribes to media query changes and attribute mutations to update state live.
  • Renders selected img with object-fit and rounded corners; fades via a short opacity transition.

Performance & Tuning

  • Observers watch only specific attributes; negligible overhead.
  • Prefer both for resilient detection across editors and published sites.
  • Use appropriate image dimensions to avoid browser scaling artifacts.

Compatibility & Limits

  • Modern browsers supporting prefers-color-scheme and MutationObserver.
  • If Framer uses custom theme hooks not covered by attributes/classes, extend the checks in code.
  • Empty state shows a neutral placeholder prompting which image is missing.

Quality

Accessibility

  • Provide meaningful alt text if the image conveys information (replace empty string).
  • Ensure adequate contrast in surrounding UI; component itself doesn’t alter image content.

Security & Privacy

  • No network calls (beyond the browser fetching your image URLs).
  • No dangerouslySetInnerHTML / eval.
  • All listeners/observers are removed on unmount.

Reference

Props

nametypedefaultdescription
lightImageimage-url””Image to show when theme is light.
darkImageimage-url””Image to show when theme is dark.
widthnumber200Rendered width of the image.
heightnumber200Rendered height of the image.
objectFitenum“cover”How the image fits its box.
radiusnumber0Corner radius applied to the <img>.
detectMethodenum“both”Theme source; both tries Framer first, then system.

Notes & Tips

  • If only one of the images is provided, the placeholder prompts you to add the other.
  • For logos, prefer contain; for photos, prefer cover.
  • If your design system sets .dark on <html> or <body>, framer detection will catch it.

Troubleshooting / FAQ

No switching occurs

Ensure detectMethod matches your environment (try both).

Dark mode not detected

Confirm that .dark class or data-theme="dark" is actually applied, or verify OS setting.

Blurred image

Provide higher-resolution assets or adjust width/height to native size.

Source

Header comment

the CMS Code-Comment · SC_Image_swither.tsx has no doc comment
/** * SC Theme Image Switcher * © SecretoCho Design 2025 (https://secretocho.design) * Swaps between light/dark images based on theme (system / Framer / both). * Listens to matchMedia and DOM attribute mutations for live updates. * @framerDisableUnlink */

props [REF]

Key excerpt

Two detectors, Framer’s theme attributes on html and body and the system’s prefers-color-scheme; “both” asks Framer first.

SC_Image_swither.tsx · lines 33–72 · tsx
        // Method 2: Check Framer's theme attributes (multiple possible implementations)        const checkFramerTheme = () => {            // Check various possible theme attributes Framer might use            const htmlElement = document.documentElement            // Common Framer theme attributes            const theme1 = htmlElement.getAttribute("data-theme")            const theme2 = htmlElement.getAttribute("data-framer-theme")            const theme3 = htmlElement.getAttribute("toggle-theme")            const theme4 = htmlElement.classList.contains("dark")            // Also check body element            const bodyTheme = document.body.getAttribute("data-theme")            const bodyDarkClass = document.body.classList.contains("dark")            return (                theme1 === "dark" ||                theme2 === "dark" ||                theme3 === "dark" ||                theme4 ||                bodyTheme === "dark" ||                bodyDarkClass            )        }        // Determine which detection method to use        const updateTheme = () => {            let isDark = false            if (detectMethod === "system") {                isDark = checkSystemTheme()            } else if (detectMethod === "framer") {                isDark = checkFramerTheme()            } else {                // "both" - Framer first, fallback to system                isDark = checkFramerTheme() || checkSystemTheme()            }            setIsDarkMode(isDark)        }

Credits

Credits

Implementation & Framer adaptation by SecretoCho Design.

inspired by
SecretoCho Design
license
SecretoCho Design

SecretoCho Design 2025

Disclaimer

Proves

Hiring a design engineer?

I’m open to senior design engineer roles: remote from Belgrade, or relocation with visa sponsorship. Write to me, or read the CV first. Client work stays selective, one project at a time.

sc@secretocho.design

read the CV · LinkedIn