Text✨ Premium

Split Color

A ::before pseudo-element clipped with clip-path: inset() splits the text into two tones; @keyframes splitWave continuously animates the split line — pure CSS, zero dependencies.

CSSColorSplit

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Text has 56 effects, including 4 free. Explore the category →

3 usage examples

Brand banner — Visual identity at large scale — Split Color example 1

① Brand banner — Visual identity at large scale

WhenYou display a product name, brand, or tagline in a hero section on a landing page, splash screen, or app launch screen.
WhyAt large scale, the two contrasting colors create immediate impact. The oscillating boundary keeps the eye engaged without overloading the message — the effect is legible even without knowing its mechanics.
Settingsfont-size 6–8rem, font-weight 900, top color #e2e8f0 (off-white) / bottom color #6366f1 (indigo), animation-duration 3s for a slower, cinematic feel.
Comparative dashboard — Binary metric heading — Split Color example 2

② Comparative dashboard — Binary metric heading

WhenIn a dashboard or financial report, a summary heading pits two metrics against each other — revenue vs. expenses, inbound vs. outbound traffic. The text is short but carries strong semantic contrast.
WhyThe animated two-tone boundary visually translates the opposition between the two metrics: the upper half (green) evokes progress, the lower half (red) the gap to close. The wave sustains attention without adding extra icons or columns — the heading itself becomes a graphical signal.
Settingsfont-size 2.8–3.2rem, font-weight 800, top color #34d399 (green — revenue) / bottom color #f87171 (rose-red — expenses), animation-duration 2.4s, letter-spacing -0.03em.
Festival poster — Artist name at XXL scale — Split Color example 3

③ Festival poster — Artist name at XXL scale

WhenOn a digital poster, album cover, or ticketing screen, an artist or band name is displayed in large letters. The surface is wide, the background dark, and visual identity takes priority.
WhyAt large size, the animated two-tone boundary gives the name a strong, memorable typographic identity: each colored half distinguishes the artist without requiring additional decorative elements. The continuous wave sustains attention like a stage light.
Settingsfont-size 7–9rem, font-weight 900, top color #f1f5f9 (bright white) / bottom color #a855f7 (purple), animation-duration 3.5s for a wide sweep, letter-spacing 0.08em.

How it works

The text is rendered twice in pixel-perfect overlay. The <span class="text-split"> element displays the upper half in its base color (color: #6366f1). Its ::before pseudo-element reproduces the same content via content: attr(data-text), positioned at position: absolute; left: 0; top: 0, and masked by clip-path: inset(50% 0 0) — which clips the top 50%, leaving only the lower half of the pseudo-element visible in the secondary color (color: #d946ef). The visual result is a single piece of text whose two halves have different colors.

The @keyframes splitWave animation oscillates the pseudo-element's clip-path between two states: inset(50% 0 0) (split at exactly 50% height) and inset(40% 0 10%) (the visible band shifts up 10%, its bottom edge also rises 10%). The colored band moves slightly upward and back, creating a gentle wave over a 2 s ease-in-out infinite cycle. The boundary is never a sharp line but shaped by the glyphs themselves.

The JS exposes a utility function splitChars(el) that wraps each character of the element's text in an individual <span class="char">. Combining the class text-split-hover on the element and the class scattered (added via JS) lets each character fade (opacity: 0.3) and move (transform) with the defined transitions — cubic-bezier(.68, -.55, .265, 1.55) for bounce. The ::before pseudo-element keeps displaying the full text via data-text, maintaining the two-tone effect even during the scatter.

The variable el = getElementById('fadeInText') at the script's top level is dormant: it returns null when no element carries that id, with no runtime error. Since the two-tone animation starts purely via CSS, no JS function call is needed to initialize it — adding the classes demo-text text-split and the data-text attribute is all that is required.

Accessibility

  • prefers-reduced-motion: absent from the code — the splitWave animation runs even when the OS requests reduced motion. Recommended fix: @media (prefers-reduced-motion: reduce) { .text-split::before { animation: none; } }.
  • The data-text attribute duplicates the visible text content: screen readers may announce the text twice. Wrap the element in a container with aria-label and add aria-hidden="true" on the colored <span> to avoid duplication.
  • Default color contrast on background #0a0a0f: #6366f1 (upper half) → ratio ≈ 5.8:1 (WCAG AA ✓); #d946ef (lower half) → ratio ≈ 5.1:1 (WCAG AA ✓). Ratios vary with your background — verify with your custom colors.
  • Keyboard navigation: the effect is purely decorative by default, no focus management is required. If you activate the hover/scatter mode (text-split-hover), make the element focusable (tabindex="0") and add an explicit :focus-visible state.
  • Semantics: <span> is a neutral element (no implicit role). If the two-tone text is a page or section title, wrap it in the appropriate heading tag (<h1><h3>) and apply the classes on an inner <span>.

Browser compatibility

Relies on clip-path: inset() and content: attr() — supported in all modern browsers since 2017–2018. Zero external dependencies.

Chrome 55+✓ Full
Firefox 47+✓ Full
Safari 9.1+✓ Full
Edge 79+✓ Full
iOS Safari 10+✓ Full
Android Chrome✓ Full

If <code>clip-path</code> is unsupported (IE 11, very old browsers), the text renders in the pseudo-element's color (<code>#d946ef</code>) without the split — readable and error-free.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="demo-preview">
  <!-- Two-tone text — CSS-only animation -->
  <span class="demo-text text-split" data-text="YOUR TEXT">YOUR TEXT</span>

  <!-- Hover/scatter variant (add class + call splitChars(el) in JS) -->
  <!-- <span class="demo-text text-split text-split-hover"
       data-text="YOUR TEXT">YOUR TEXT</span> -->
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
color on .text-split #6366f1 Color of the upper text half (base element). Change the hex value directly in the CSS.
color on .text-split::before #d946ef Color of the lower half (::before pseudo-element). Should contrast with the base color for the effect to be legible.
font-size on .demo-text 2.5rem Text size. The effect reads best at large sizes (4–10 rem) where the two-tone boundary is clearly visible.
animation-duration in .text-split::before 2s Duration of one wave cycle. 1 s = fast and dynamic; 4–6 s = slow and ambient. Replace the first value of the animation shorthand.
inset() in @keyframes splitWave inset(50% 0 0) / inset(40% 0 10%) First value = boundary position (50% = midpoint). Increase to shrink the lower zone, decrease to expand it. Remove the animation and set a fixed value for a static split.
data-text on the element "SPLIT" Text content of the ::before pseudo-element. Must always match the element's visible textContent — a mismatch offsets the two layers.
font-weight on .demo-text 800 Font weight. At 800–900 the two colors are clearly distinct. Below 600, the boundary may look blurry on fonts with low weight contrast.
.text-split-hover class absent Add this class to the element and call splitChars(el) in JS to enable per-character scatter on hover. Then toggle .scattered via JS to trigger the fade + transform animation.

FAQ

Yes, fully. The splitWave animation is driven entirely by CSS. The data-text attribute must match the element's visible text, and the two classes demo-text text-split are all that is needed. The JS in the snippet is only useful if you want to activate the per-character scatter via splitChars(el).
Modify the first value of clip-path in @keyframes splitWave. For example, inset(30% 0 0) expands the lower zone to 70% of the height; inset(70% 0 0) reduces it to 30%. To remove the animation and lock a static boundary, drop the animation property and write .text-split::before { clip-path: inset(50% 0 0); } directly.
clip-path: inset() applies a single rectangular clip across the entire pseudo-element. On two lines, the boundary cuts at the same absolute level — line 1 may be fully upper-color and line 2 fully lower-color depending on the chosen value. For per-line control, a background: linear-gradient() combined with -webkit-background-clip: text; -webkit-text-fill-color: transparent gives more granular results.