Source Code

<div class="cp-row">
  <div class="cp-preview" data-theme="light">
    <span class="cp-preview-label">Light</span>
    <article class="cp-card">
      <div class="cp-avatar">MK</div>
      <h3>Maya Kessler</h3>
      <p class="cp-role">Product Designer</p>
      <p class="cp-bio">Designs interfaces for climate and energy startups. Based in Berlin.</p>
      <div class="cp-tags">
        <span class="cp-tag">Figma</span>
        <span class="cp-tag">Design Systems</span>
      </div>
      <button class="cp-btn">View profile</button>
    </article>
  </div>

  <div class="cp-preview" data-theme="dark">
    <span class="cp-preview-label">Dark</span>
    <article class="cp-card">
      <div class="cp-avatar">MK</div>
      <h3>Maya Kessler</h3>
      <p class="cp-role">Product Designer</p>
      <p class="cp-bio">Designs interfaces for climate and energy startups. Based in Berlin.</p>
      <div class="cp-tags">
        <span class="cp-tag">Figma</span>
        <span class="cp-tag">Design Systems</span>
      </div>
      <button class="cp-btn">View profile</button>
    </article>
  </div>
</div>

Card Component Light/Dark Preview — Free HTML CSS JS Snippet

Card Component Light/Dark Preview · Cards · Plain HTML, CSS & JS · Live preview

What's included

Features

Identical card markup and CSS rendered twice under independently scoped data-theme attributes
No JavaScript toggle required — both themes are visible simultaneously by default
Each preview wrapper defines its own complete set of CSS custom properties
Proves theme correctness by construction: the only difference between the two cards is variable scope
Realistic profile card content: avatar, name, role, bio, tags, and a call-to-action button
Responsive stacking of the two previews on narrow viewports
Directly reusable pattern for style guides, design QA pages, and component documentation
Zero global state — scoped attributes mean this pattern composes safely with any page-level theme system

About this UI Snippet

Card Component Light/Dark Preview — Side-by-Side Scoped Theme Comparison

Screenshot of the Card Component Light/Dark Preview snippet rendered live

Reviewing a component's dark mode usually means flipping a global toggle back and forth and relying on memory to compare the two states. This snippet instead renders the identical card markup twice, each instance wrapped in its own preview container with a fixed data-theme attribute — "light" on one, "dark" on the other — so both variants are visible at the same time for direct, side-by-side comparison.

Scoped theming instead of a global switch

Each .cp-preview wrapper defines its own full set of CSS custom properties (surface, text, muted, border, accent, tag background), with the dark wrapper overriding all of them via a [data-theme="dark"] attribute selector scoped to that specific wrapper. Because the attribute lives on the wrapper rather than a shared ancestor like <html> or <body>, the two previews never interfere with each other — there is no global state to toggle, and no JavaScript is needed to produce the comparison at all.

Identical markup and CSS prove the theming actually works

Both cards use byte-for-byte the same HTML structure and the same .cp-card CSS rules. The only difference between them is which custom-property values are in scope where they render — which is exactly the property a token-based design system is supposed to guarantee: components shouldn't need theme-specific markup or theme-specific CSS classes, only theme-scoped variable values.

Practical for design review and QA

This pattern is directly useful for style-guide pages, design QA, and pull-request screenshots — anywhere a reviewer needs to confirm that a component reads correctly in both themes without manually toggling anything or trusting a memory of "how it looked a moment ago."

Step by step

How to Use

  1. 1
    Load the snippetClick "Card Component Light/Dark Preview" in the sidebar to load its HTML, CSS, and JS into the editor panels. The preview updates instantly.
  2. 2
    Edit the codeModify any panel — HTML, CSS, or JS. The preview refreshes as you type. Use Reset in each panel header to restore the original.
  3. 3
    Preview on devicesClick the Mobile (375px), Tablet (768px), or Desktop buttons in the preview header to check responsiveness.
  4. 4
    Export in your formatClick "HTML" to download a standalone file, "JSX" for a React component, "Tailwind" for a React + Tailwind CSS component, "Tailwind HTML" for a standalone HTML file with Tailwind CDN, "Vue" for a Vue 3 SFC with <template>/<script setup>/<style scoped>, or "Angular" for a standalone Angular .component.ts file. "Copy all" copies the full code to clipboard.
  5. 5
    Save your versionClick "Save as", type a name, and press Enter. Your snippet saves to IndexedDB and appears in the Saved tab.

Real-world uses

Common Use Cases

Design QA and style guides
Verify a component renders correctly in both themes without toggling anything or relying on memory.
Component documentation pages
Show both theme variants of a component simultaneously in a Storybook-style reference page.
Teaching scoped CSS custom properties
A clear, minimal example of theme scoping via an attribute on a wrapper rather than a global toggle.
Pull request screenshots and reviews
Capture both light and dark renders of a component in one screenshot for reviewers.

Got questions?

Frequently Asked Questions

The goal is direct visual comparison — seeing both themes at once rather than switching between them and relying on memory. Each preview wrapper carries its own fixed data-theme attribute so both render permanently in their respective themes.

Each .cp-preview wrapper defines its own set of CSS custom properties, and a [data-theme="dark"] attribute selector scoped to that same wrapper overrides them. Since the attribute lives on the wrapper itself rather than a shared ancestor, the two previews never affect each other.

The card's own CSS rules never reference "light" or "dark" directly — they only reference custom properties via var(). This is what proves the same component markup and stylesheet correctly adapts to whichever theme context it happens to be rendered inside.

Yes — duplicate a .cp-preview block, give it a distinct data-theme value (e.g. "high-contrast"), and add a matching CSS override block redefining the same custom properties for that value.