20 CSS Text Gradient Effects20 / 20

CSS + JSMIT licensed

Dark Mode vs Light Mode CSS Text Gradient

A toggle-enabled card that switches between vivid neon gradients (dark) and rich jewel-tone gradients (light) via CSS variables and a JS class swap.

Published

Live Demo
Try it

The code

<div class="tg-20" id="tg-20-root">
  <div class="tg-20__card">
    <div class="tg-20__header">
      <div class="tg-20__brand">
        <span class="tg-20__icon">◈</span>
        <span class="tg-20__brand-name"><span class="tg-20__grad">Lumis</span></span>
      </div>
      <button class="tg-20__toggle" id="tg-20-toggle" aria-label="Toggle colour scheme">
        <span class="tg-20__toggle-icon tg-20__moon">☾</span>
        <span class="tg-20__toggle-icon tg-20__sun">☀</span>
      </button>
    </div>
    <h1 class="tg-20__title">Adaptive <span class="tg-20__grad">Colour</span><br>for Every Context</h1>
    <p class="tg-20__body">Gradient variables swap automatically between light and dark schemes via <code>prefers-color-scheme</code> and a JS toggle. Vivid saturated gradients work on dark backgrounds; softer, deeper tones on light.</p>
    <div class="tg-20__chips">
      <span class="tg-20__chip tg-20__grad-text">Dark: vivid neon</span>
      <span class="tg-20__chip tg-20__grad-text-alt">Light: rich jewel</span>
    </div>
  </div>
</div>
.tg-20,
.tg-20 *,
.tg-20 *::before,
.tg-20 *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
/* Dark defaults */

.tg-20 {
  /* Dark-mode gradient: vivid, high-saturation */
  --grad: linear-gradient(110deg, #f0abfc 0%, #818cf8 50%, #38bdf8 100%);
  --grad-alt: linear-gradient(110deg, #34d399 0%, #06b6d4 100%);
  --bg: #06030f;
  --surface: #10101a;
  --text: #f0e7ff;
  --muted: rgba(240,231,255,.5);
  --border: rgba(255,255,255,.08);
  --toggle-bg: rgba(255,255,255,.07);
  --moon-op: 1;
  --sun-op: 0;
  font-family: system-ui, -apple-system, sans-serif;
  background: var(--bg);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 44px 24px;
  transition: background .3s, color .3s;
}
/* Light-mode override via media query */

@media (prefers-color-scheme: light) {
  .tg-20:not(.tg-20--force-dark) {
    --grad: linear-gradient(110deg, #4f46e5 0%, #7c3aed 55%, #a21caf 100%);
    --grad-alt: linear-gradient(110deg, #0f766e 0%, #0284c7 100%);
    --bg: #f8fafc;
    --surface: #ffffff;
    --text: #0f172a;
    --muted: #64748b;
    --border: #e2e8f0;
    --toggle-bg: #e2e8f0;
    --moon-op: 0;
    --sun-op: 1;
  }
}
/* JS-toggled light class */

.tg-20--light {
  --grad: linear-gradient(110deg, #4f46e5 0%, #7c3aed 55%, #a21caf 100%);
  --grad-alt: linear-gradient(110deg, #0f766e 0%, #0284c7 100%);
  --bg: #f8fafc;
  --surface: #ffffff;
  --text: #0f172a;
  --muted: #64748b;
  --border: #e2e8f0;
  --toggle-bg: #e2e8f0;
  --moon-op: 0;
  --sun-op: 1;
}

.tg-20__card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 36px 32px;
  max-width: 520px;
  width: 100%;
  transition: background .3s, border-color .3s;
}

.tg-20__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 28px;
}

.tg-20__brand {
  display: flex;
  align-items: center;
  gap: 8px;
}

.tg-20__icon {
  font-size: 1.3rem;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.tg-20__brand-name {
  font-size: 1.25rem;
  font-weight: 800;
}
/* Reusable gradient text — driven by --grad variable */

.tg-20__grad {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

.tg-20__grad-text {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.tg-20__grad-text-alt {
  background: var(--grad-alt);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.tg-20__toggle {
  background: var(--toggle-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  cursor: pointer;
  transition: background .2s;
  position: relative;
  width: 44px;
  height: 36px;
}

.tg-20__toggle-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  font-size: 1rem;
  transition: opacity .25s;
}

.tg-20__moon {
  opacity: var(--moon-op);
}

.tg-20__sun {
  opacity: var(--sun-op);
}

.tg-20__title {
  font-size: clamp(1.6rem, 5vw, 2.5rem);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -.03em;
  color: var(--text);
  margin-bottom: 16px;
  transition: color .3s;
}

.tg-20__body {
  font-size: .875rem;
  color: var(--muted);
  line-height: 1.7;
  margin-bottom: 22px;
  transition: color .3s;
}

.tg-20__body code {
  font-size: .8em;
  background: var(--border);
  border-radius: 3px;
  padding: 1px 5px;
  color: inherit;
  filter: brightness(1.5);
}

.tg-20__chips {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.tg-20__chip {
  font-size: .8rem;
  font-weight: 700;
  padding: 5px 14px;
  border-radius: 99px;
  border: 1px solid var(--border);
}

@media (prefers-reduced-motion: reduce) {
  .tg-20,
    .tg-20__card,
    .tg-20__title,
    .tg-20__body,
    .tg-20__toggle {
    transition: none;
  }
}
(function(){
  var root = document.getElementById('tg-20-root');
  var btn  = document.getElementById('tg-20-toggle');
  var light = false;
  btn.addEventListener('click', function(){
    light = !light;
    root.classList.toggle('tg-20--light', light);
    root.classList.toggle('tg-20--force-dark', !light);
  });
})();
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Text Gradient Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Dark Mode vs Light Mode CSS Text Gradient
Source: https://codefronts.com/motion/css-text-gradient-effect/dark-mode-vs-light-mode-css-text-gradient/

A toggle-enabled card that switches between vivid neon gradients (dark) and rich jewel-tone gradients (light) via CSS variables and a JS class swap.
## HTML
```html
<div class="tg-20" id="tg-20-root">
  <div class="tg-20__card">
    <div class="tg-20__header">
      <div class="tg-20__brand">
        <span class="tg-20__icon">◈</span>
        <span class="tg-20__brand-name"><span class="tg-20__grad">Lumis</span></span>
      </div>
      <button class="tg-20__toggle" id="tg-20-toggle" aria-label="Toggle colour scheme">
        <span class="tg-20__toggle-icon tg-20__moon">☾</span>
        <span class="tg-20__toggle-icon tg-20__sun">☀</span>
      </button>
    </div>
    <h1 class="tg-20__title">Adaptive <span class="tg-20__grad">Colour</span><br>for Every Context</h1>
    <p class="tg-20__body">Gradient variables swap automatically between light and dark schemes via <code>prefers-color-scheme</code> and a JS toggle. Vivid saturated gradients work on dark backgrounds; softer, deeper tones on light.</p>
    <div class="tg-20__chips">
      <span class="tg-20__chip tg-20__grad-text">Dark: vivid neon</span>
      <span class="tg-20__chip tg-20__grad-text-alt">Light: rich jewel</span>
    </div>
  </div>
</div>
```
## CSS
```css
.tg-20,
.tg-20 *,
.tg-20 *::before,
.tg-20 *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
/* Dark defaults */

.tg-20 {
  /* Dark-mode gradient: vivid, high-saturation */
  --grad: linear-gradient(110deg, #f0abfc 0%, #818cf8 50%, #38bdf8 100%);
  --grad-alt: linear-gradient(110deg, #34d399 0%, #06b6d4 100%);
  --bg: #06030f;
  --surface: #10101a;
  --text: #f0e7ff;
  --muted: rgba(240,231,255,.5);
  --border: rgba(255,255,255,.08);
  --toggle-bg: rgba(255,255,255,.07);
  --moon-op: 1;
  --sun-op: 0;
  font-family: system-ui, -apple-system, sans-serif;
  background: var(--bg);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 44px 24px;
  transition: background .3s, color .3s;
}
/* Light-mode override via media query */

@media (prefers-color-scheme: light) {
  .tg-20:not(.tg-20--force-dark) {
    --grad: linear-gradient(110deg, #4f46e5 0%, #7c3aed 55%, #a21caf 100%);
    --grad-alt: linear-gradient(110deg, #0f766e 0%, #0284c7 100%);
    --bg: #f8fafc;
    --surface: #ffffff;
    --text: #0f172a;
    --muted: #64748b;
    --border: #e2e8f0;
    --toggle-bg: #e2e8f0;
    --moon-op: 0;
    --sun-op: 1;
  }
}
/* JS-toggled light class */

.tg-20--light {
  --grad: linear-gradient(110deg, #4f46e5 0%, #7c3aed 55%, #a21caf 100%);
  --grad-alt: linear-gradient(110deg, #0f766e 0%, #0284c7 100%);
  --bg: #f8fafc;
  --surface: #ffffff;
  --text: #0f172a;
  --muted: #64748b;
  --border: #e2e8f0;
  --toggle-bg: #e2e8f0;
  --moon-op: 0;
  --sun-op: 1;
}

.tg-20__card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 36px 32px;
  max-width: 520px;
  width: 100%;
  transition: background .3s, border-color .3s;
}

.tg-20__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 28px;
}

.tg-20__brand {
  display: flex;
  align-items: center;
  gap: 8px;
}

.tg-20__icon {
  font-size: 1.3rem;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.tg-20__brand-name {
  font-size: 1.25rem;
  font-weight: 800;
}
/* Reusable gradient text — driven by --grad variable */

.tg-20__grad {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

.tg-20__grad-text {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.tg-20__grad-text-alt {
  background: var(--grad-alt);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.tg-20__toggle {
  background: var(--toggle-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  cursor: pointer;
  transition: background .2s;
  position: relative;
  width: 44px;
  height: 36px;
}

.tg-20__toggle-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  font-size: 1rem;
  transition: opacity .25s;
}

.tg-20__moon {
  opacity: var(--moon-op);
}

.tg-20__sun {
  opacity: var(--sun-op);
}

.tg-20__title {
  font-size: clamp(1.6rem, 5vw, 2.5rem);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -.03em;
  color: var(--text);
  margin-bottom: 16px;
  transition: color .3s;
}

.tg-20__body {
  font-size: .875rem;
  color: var(--muted);
  line-height: 1.7;
  margin-bottom: 22px;
  transition: color .3s;
}

.tg-20__body code {
  font-size: .8em;
  background: var(--border);
  border-radius: 3px;
  padding: 1px 5px;
  color: inherit;
  filter: brightness(1.5);
}

.tg-20__chips {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.tg-20__chip {
  font-size: .8rem;
  font-weight: 700;
  padding: 5px 14px;
  border-radius: 99px;
  border: 1px solid var(--border);
}

@media (prefers-reduced-motion: reduce) {
  .tg-20,
    .tg-20__card,
    .tg-20__title,
    .tg-20__body,
    .tg-20__toggle {
    transition: none;
  }
}
```

## JavaScript
```js
(function(){
  var root = document.getElementById('tg-20-root');
  var btn  = document.getElementById('tg-20-toggle');
  var light = false;
  btn.addEventListener('click', function(){
    light = !light;
    root.classList.toggle('tg-20--light', light);
    root.classList.toggle('tg-20--force-dark', !light);
  });
})();
```

How this works

The light/dark swap is driven by two CSS custom property sets on the .tg-20 root element. The dark defaults are declared at the root; a @media (prefers-color-scheme: light) block overrides them for system-preference-light users. A JavaScript toggle adds/removes the .tg-20--light class, which contains a third identical property override block, allowing runtime switching independent of the OS setting.

Dark-mode gradients use vivid, high-lightness pastels (70–80% HSL lightness) that pop against deep backgrounds without needing high saturation. Light-mode gradients use darker, richer jewel tones (30–50% lightness) to maintain contrast against light surfaces. Using CSS custom properties on the wrapper element means all gradient consumers — headings, icons, chips — update simultaneously with a single class change on the root.

Make it yours

  • Add a third mode (e.g. 'contrast' or 'vibrant') by defining a fourth CSS variable block in a .tg-20--vibrant class and cycling through all modes in the toggle JS.
  • Use transition: --g-a .4s, --g-b .4s (requires @property registration) for smooth gradient variable interpolation between dark and light modes — requires Chrome 85+.
  • Extend the media query approach to swap fonts as well: define --font-weight: 800 in dark mode and --font-weight: 700 in light mode so text weight adapts alongside the gradient intensity.
  • Save the user's preference to localStorage and read it on page load so the chosen mode persists across sessions without re-clicking the toggle.
  • Test your actual OS setting by opening the demo in an incognito window — it will default to whatever prefers-color-scheme the OS reports, bypassing the JS toggle state.

Gotchas — read before shipping

  • CSS custom properties used in gradient definitions (e.g. var(--grad)) cannot be transitioned between values in most browsers without registering them via @property. The class-swap approach used here achieves an instant switch, not a smooth interpolation.
  • The --moon-op and --sun-op approach for toggling icons relies on setting opacity to 0 or 1 rather than using display: none, ensuring the toggle button size remains stable regardless of which icon is visible.
  • When persisting the mode to localStorage, be aware that prefers-color-scheme media queries still run at page load before JS executes. Add the class to document.documentElement immediately from a <script> in <head> to prevent a flash of the wrong mode.

Browser support

ChromeSafariFirefoxEdge
69+12.1+83+69+

CSS custom property variable-based gradient swapping is instant (no interpolation) in all browsers; smooth gradient transitions require @property registration available in Chrome 85+ only.

Techniques used in this demo

Search CodeFronts

Loading…