While Framer makes it incredibly easy to swap colors between light and dark themes using the built-in Styles panel, swapping content (like a specific image, logo variant, or complex illustration) is still a bit of a challenge.
In this tutorial, I’ll share a custom code override I use to conditionally render layers based on the active theme.
I’ve included a remix link at the very bottom of this post so you can clone the project and see the logic in action.
In this tutorial, you will:
- Add a theme toggle button to control the theme.
- Apply code overrides directly to elements to control visibility.
- Apply the code override not only to images but also to other elements.
The Code: Theme Visibility Override
Create a new code file named ThemeVisibility and paste the following:
import { useEffect, useState } from "react"
import type { ComponentType } from "react"
function getTheme(): "light" | "dark" | null {
if (typeof document === "undefined") return null
const value = document.body?.getAttribute("toggle-theme")
return (value === "light" || value === "dark") ? value : null
}
function useBodyTheme() {
const [theme, setTheme] = useState<"light" | "dark" | null>(() => getTheme())
useEffect(() => {
if (typeof document === "undefined") return
const update = () => setTheme(getTheme())
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.type === "attributes" && m.attributeName === "toggle-theme") {
update()
break
}
}
})
observer.observe(document.body, { attributes: true, attributeFilter: ["toggle-theme"] })
return () => observer.disconnect()
}, [])
return theme
}
export function VisibleOnDark(Component: ComponentType): ComponentType {
return (props) => {
const theme = useBodyTheme()
return theme === "dark" ? <Component {...props} /> : null
}
}
export function VisibleOnLight(Component: ComponentType): ComponentType {
return (props) => {
const theme = useBodyTheme()
return theme === "light" ? <Component {...props} /> : null
}
}
How to Apply the Override
Unlike standard styling, this logic removes the element from the DOM entirely when the theme doesn't match, ensuring a clean performance profile.
Select Your Element: Click on the specific layer (image, frame, or component) you want to toggle.

Attach the Override: In the Overrides section of the Properties panel, select the ThemeVisibility file.
Choose the Mode:
- Assign
VisibleOnLightto your light-mode specific content. - Assign
VisibleOnDarkto your dark-mode specific content.


Flexible Placement: These elements do not need to be stacked; they can live anywhere in your layout. If the condition isn't met, the element simply won't render, and the rest of your layout will adjust accordingly.

Beyond Images: Dynamic Text Swapping
This logic isn't limited to visual assets. You can also use the code override to display different variants of text. If your dark mode requires a more concise headline or a specific call-to-action that reads better against deep backgrounds, simply apply the override to your text layers.

Why this works
Unlike simply changing the "Opacity" to 0, this code returns null if the theme doesn't match. This means the browser doesn't just hide the element—it doesn't render it at all, which is better for performance and ensures there are no invisible "ghost" elements blocking clicks on your site.
To see exactly how it's set up, you can [grab the remix here] and bring these overrides directly into your own design.
Need help building a custom Framer site? Reach out to Nexio Studio and let's build something exceptional.