CSS & Front-End

CSS Gradient Generator: Gradients That Do Not Band

A gradient that looked smooth in the design tool grows stripes on the page. The cause is arithmetic: 8 bit colour, two similar colours and 1600 pixels to cover. How to count the steps before you ship, the three real fixes, and a builder that measures the banding for you.

CSS Gradient Generator: Gradients That Do Not Band

You build a header gradient in the design tool. Two dark blues, top to bottom, perfectly smooth on the canvas. You paste the rule into the theme, load the page, and the gradient has grown stripes: four or five flat bands with a visible edge between each one.

Nothing was lost on the way. The gradient is exactly the one you drew. What changed is the distance it has to cover. Take a real pair of colours, #101828 to #1D2A44. Red moves 13 levels, green 18, blue 28. Twenty eight is the largest of the three, so the whole ramp contains 29 distinct colours and not one more. Spread 29 colours across a 1600 pixel header and each one owns 57 pixels of screen. That is not a gradient, it is a staircase with very wide steps.

Banding is arithmetic, not craft. Once you can count the steps in a colour pair you know, before you ship, whether the gradient will be smooth and which of the three real fixes you need.

The builder below does the counting. Pick the type, set the angle, edit the stops, and it reports how many distinct 8 bit steps the gradient produces, how wide one step lands, and which pair of stops will band. Then it hands you the CSS, and a PNG if you need a file.

Where the steps come from

A browser stores each channel of a colour in 8 bits: 256 values per channel, 0 to 255, with nothing in between. There is no 40.5 in the blue channel, not on an ordinary display path. A gradient is therefore not a continuous ramp. It is the shortest staircase joining your two colours, and the number of stairs is fixed by the colours themselves, not by the size of the element you paint them into.

You can work it out on paper. Take the absolute difference in each channel and keep the largest: steps = max(|dR|, |dG|, |dB|). Black to white gives 255. The pair #3B66FF to #6E91FF gives 51, because blue is pinned at 255 in both and only red travels far. Two dark navies usually land between 20 and 40. Divide the on screen length of the gradient by that number and you have the width of one band.

Above roughly 3 pixels per band the edges start to show, and your visual system helps them along. Mach banding is the effect: lateral inhibition exaggerates the boundary between two nearly identical flat areas, so a step of one level out of 256 reads as a drawn line rather than a change of colour.

CSS gradient generator

Build a linear, radial or conic gradient, see exactly where it will band, then copy the CSS or save a PNG. Click the bar to drop a stop where you click and drag the handles to move them. Everything is drawn in this browser tab and nothing is uploaded.

Preview

Reading the gradient.

Colour stops

A click on the bar adds a stop right there, in the colour the gradient already has at that point, so nothing changes until you move it. Drag a handle to shift a stop. Double click a handle, or press Delete while it holds focus, to remove it. Arrow keys nudge the focused handle by one per cent, with Shift by ten.

Selected stop 1 of 2
2 of 8 stops
Gradient type

0 points up, 90 points right, 180 points down.

Banding fix

Banding happens when too few 8 bit steps are stretched over too many pixels. A tiled noise layer at about 6 per cent opacity breaks the step edges up. Switching it on adds the extra lines to the CSS below and bakes the same noise into the PNG.

PNG size
WunderPaint
The Dynamic Design and Automation Studio
WunderPaint is a layered image editor for your WordPress media library. WunderPaint Studio is the same thing in any browser, free and without an account.

What the readout under the preview counts

The step count is measured, not guessed from the hex values. The tool samples the resolved gradient along the axis it is painted on, at the size the preview is on your screen, and counts distinct rounded RGB triples. Axis length depends on the type: linear uses the gradient line length for that angle and box, the width times the sine of the angle plus the height times the cosine; radial uses the radius out to the farthest corner; conic uses the circumference, because the colour travels around the circle.

The warning is narrower than the step count, deliberately. It fires when one adjacent pair of stops produces bands 3 pixels or wider across a gap of at least 8 per cent of the axis: close in colour, far apart in space, which is the exact recipe for a stripe. Two identical stops are a flat segment rather than a gradient, so they are not counted at all.

Because the measurement uses the real width of the preview, the number moves with the viewport and is recomputed on resize. The default blue ramp warns at 4.5 pixel bands in an 800 pixel preview and comes up clean at 2.3 pixels when the same preview is 380 pixels wide on a phone. That is not sloppiness, it is the gradient: the same colours band across a desktop hero and stay clean inside a mobile card.

All of it happens in your browser tab. Nothing is uploaded, there is no server round trip, and nothing is stored between visits.

Dark, flat and wide is the worst case

Three properties make banding visible, and they tend to arrive together. The first is a short colour distance. Subtle ramps are subtle because the two colours sit close together in code values, which means few steps. #FFFFFF to #F2F4F8, the ramp people use to lift a section off a white page, moves 13 levels in red, 11 in green and 7 in blue. Thirteen steps across a 1600 pixel section is one band every 123 pixels.

The second is flat colour. Photographs band too, but grain and detail break the edges up before you notice. A flat gradient has nothing to hide behind, which is why the complaint is always a hero section, a card background or a rendered sky, never a portrait.

The third is the screen. Plenty of laptop panels are 6 bit with frame rate control: 64 real levels per channel, the rest faked by flickering between two neighbours. On those displays a 28 step ramp can arrive as seven or eight bands no matter how clean your CSS is. You cannot fix somebody else’s panel, which is the argument for the fix that works on all of them.

Table of five colour pairs showing the 8 bit step count for each, the resulting band width across 1600 pixels, and whether the banding is visible, with the formula steps equals the largest channel difference

Three fixes, and the one that works everywhere

The first fix people reach for is more stops, and it is mostly wrong. Adding a stop halfway between the two colours creates no levels, because none were missing: the ramp already passes through every value between the endpoints. An intermediate stop helps only when it changes the path or the pacing. Routing through a colour that moves a different channel adds genuine steps. Pulling the outer stops inwards shortens the run, so from 0 and 100 per cent to 20 and 80 per cent on a 1600 pixel box turns 57 pixel bands into 34 pixel bands. Better, still visible, and now with a flat edge at each end.

The second is a wider colour space. CSS Color 4 lets you interpolate somewhere other than sRGB, as in linear-gradient(in oklab, #101828, #1D2A44), and it genuinely changes gradients: it is the cure for the muddy grey midpoint two complementary hues produce in sRGB. It is not a cure for banding. The result is still quantised to whatever the display path offers, which is 8 bits per channel on most machines. A 10 bit path helps, but only when panel, operating system and browser all agree, which you cannot know from the server.

The third is noise, and it is the one that works. Add a small random variation per pixel and the boundary between two steps stops being a straight line: some pixels on the light side fall dark, some on the dark side fall light, and the edge dissolves into texture the eye reads as a smooth transition. This is dithering, the same trick that let 256 colour GIFs carry photographs. It needs no bit depth, no colour management, no recent browser and no cooperation from the panel.

The Banding fix checkbox is that idea done in CSS. It stacks a tiled 160 pixel SVG feTurbulence layer at 6 per cent opacity above the gradient, and the extra background-image, background-repeat and background-size lines appear in the CSS output. Over a flat 128 grey patch it takes the variance from zero to roughly plus or minus 3 levels, enough to break a step edge apart. It is not colour neutral: it lifts a flat colour by about two levels, so look at a dark ramp again after switching it on. The PNG export uses per pixel random noise of about plus or minus 2 levels instead of the tiled SVG, so the two match in effect, not pixel for pixel.

/* the shape of the dithered output: noise layer first, gradient underneath */
background-image:
  url("data:image/svg+xml,... feTurbulence ..."),
  linear-gradient(160deg, #101828 0%, #1D2A44 100%);
background-repeat: repeat, no-repeat;
background-size: 160px 160px, auto;

The angle points the gradient line, not the light

In linear-gradient(45deg, ...) the angle is the direction the gradient line points, which is the direction of the last colour. 0deg points to the top, 90deg to the right, and the angle increases clockwise, so 45deg starts at the bottom left and finishes at the top right. Most people expect the opposite, because they are picturing a light source in the top left corner. The default reinforces the confusion: with no angle at all a linear gradient runs to bottom, which is 180deg.

The keyword forms are not the angles they look like either. to top right means 45deg only when the box is square. The specification puts the gradient line perpendicular to the line joining the two neighbouring corners, so on a 1600 by 200 hero to top right resolves to 7.1 degrees, almost straight up. Rendered against linear-gradient(7.125deg, ...) the two are identical to within one level of noise. The tool works in numbers only: the angle field wraps modulo 360, so 400 becomes 40 and -30 becomes 330, and eight presets cover the usual directions.

That angle drives linear and conic gradients, and for a conic gradient it is the from angle, with the colour turning clockwise from there. Radial gradients have no angle, so the field is switched off with a line of text saying why, rather than sitting there ignoring you. Radial previews and PNG exports size to the farthest corner, which is what CSS does by default.

Stop positions carry one more trap. The CSS output keeps the numbers you typed, but a browser never lets a stop sit behind the one before it: the later stop is clamped up to the earlier one. Typing 70 per cent and then 20 per cent gives a hard edge at 70 per cent, not a reversal, and the preview, the PNG and the readout all use that resolved order. Hex fields take three or six digits with or without the hash, write themselves back in full form on commit, and turn red and are ignored while the text is not a colour.

/* stop two sits behind stop one, so the browser clamps it */
background: linear-gradient(90deg, #3B66FF 70%, #6E91FF 20%, #FFFFFF 100%);
/* what you get: solid blue to 70%, a hard edge, then a ramp to white */

A gradient should almost never be an image file

Everything above assumes the gradient stays as CSS. Export the same ramp as a JPEG and it gets measurably worse, for a reason worth knowing.

Here is the measurement, on the ramp from the top of this article. Painted as a CSS gradient across 1600 pixels and screenshotted, one row of pixels holds 80 distinct values with no flat run longer than 3 pixels: Chromium dithers gradients as it paints them, which is why the screen beats the 29 colour theory. Push those same pixels through JPEG at quality 80 and the row collapses to 28 distinct values in 28 flat runs, the widest 136 pixels across. At quality 60 it is 20 values and a 200 pixel run. The encoder does not merely fail to help. It removes the dithering the browser had already done.

The mechanism is the block transform. JPEG cuts the image into 8 by 8 blocks and describes each block as a sum of cosine patterns. A gentle ramp inside one block is carried by coefficients of very small amplitude, and small coefficients are the first thing the quantiser rounds to zero. Once they are gone the block reverts to its average colour and goes flat, which is precisely a band. Chroma subsampling at 4:2:0 halves colour resolution in both directions on top of that, so a gradient that shifts hue fares worst of all. The comparison of WebP, AVIF, JPEG and PNG covers what each encoder keeps and discards.

WordPress then repeats the damage. Upload that JPEG and core builds the whole size set from it, thumbnail, medium, medium_large, large and every size the theme registers, re-encoding each one through WP_Image_Editor at a default quality of 82, or 86 for WebP. Anything wider than the 2560 pixel big_image_size_threshold also gets a re-saved -scaled original. That is five or more files from one upload, each a fresh pass of the same quantiser. srcset then hands a different one of them to every visitor, so the file you inspected is not the file most people see.

Against all that, the CSS rule is 49 bytes, makes no HTTP request, needs no srcset, no retina copy and no regeneration, and repaints at any container size. There is almost no case for the file. When there is one, an email template, a PDF, a print layout or an OG image that has to be a real file, use Download PNG: PNG is lossless, so the noise survives, the default is 1600 by 900, and each dimension clamps between 16 and 8000 pixels.

Table comparing the same dark blue ramp delivered as a CSS gradient, a PNG, JPEG at quality 80 and 60, and a JPEG with noise added first, listing distinct pixel values, longest flat run and file weight

If the library already holds a dozen exported gradient JPEGs from before you knew any of this, the awkward part is not deleting them, it is working out which posts still point at them. That is what the usage analysis in WunderPaint’s media library manager is for.

What the tool leaves out on purpose

Stops are hex only, so there is no alpha and no rgba. A gradient that fades to transparent is one line of hand written CSS anyway. There are no interpolation hints, the bare percentage between two stops that shifts the midpoint, and no oklab or oklch interpolation. No repeating gradients, no stacking of several gradients into one background, no dragging stops around the preview, and nothing is remembered between visits because nothing is stored. Stops cap at eight and floor at two, so Add stop switches off at eight and the remove buttons switch off at two.

Those omissions matter in exactly one situation: a hue path with a bad middle. Blue to yellow through sRGB passes a dead grey, and the fix there is in oklch or a deliberate intermediate stop, not noise. Everything else the tool covers: two to eight opaque stops, linear, radial or conic, an angle, an honest measurement and a file when you want one.

Back to the header

Count first. Two colours, the largest channel difference, the width of the element: that is the entire prediction and it takes ten seconds. If the answer is more than about 3 pixels per band you know what the page will look like before you load it, and you know it for the desktop width specifically, because the same ramp inside a narrow card is fine.

Then choose deliberately instead of nudging colours until they look acceptable on your monitor. Shorten the run or move the stops if the layout allows it. Change the colours if the design allows it. If neither is available, and with dark ramps neither usually is, switch the noise on and accept a two level shift in flat colour that nobody will notice, in exchange for edges nobody will notice either.

And keep it as CSS. A gradient is 49 bytes of instruction that the browser paints at the size the container turns out to be, with its own dithering applied for free. Turning it into a JPEG throws that dithering away, adds a request, adds five files to the media library, and hands a lossy encoder a picture composed entirely of the one thing it is built to discard.

CSS Gradient Generator: Gradients That Do Not Band

Table of Contents

Learn it by building something

Every week one thing you can make the same afternoon, from dynamic templates to 3D type. Written down step by step.

One mail a week, and then it ends.
Unsubscribe in one click.

Developer Tools

Dummy Text Generator: Lorem Ipsum That Behaves Like Real Copy

Lorem ipsum has a longest word of thirteen letters and no umlauts at all, so a layout tested with it is tested with the easiest text it will ever hold. What placeholder text should actually prove, and a generator that produces it.

Photo Editing

Turn a phone photo of a document into something that looks scanned

A photographed page is tilted, tapered, grey and shadowed, and all three faults are completely determined. Four corners give eight equations, a heavy blur estimates the lighting so it can be divided out, and Otsu picks the threshold. No model, no download, and it finishes before you let go of the button.

SEO & Structured Data

WordPress Alt Text: Where It Lives and Why Your Edits Do Not Show

Alt text in WordPress is one postmeta row, copied into the post markup the moment you insert an image. That single fact explains why editing the library changes nothing on pages you already published, why an audit has two halves, and what to fix first.

Troubleshooting

Why Is Your WordPress Site Slow? A Diagnostic Order, Not a Checklist

WordPress performance advice is usually handed over as a flat, alphabetized checklist. This walks through the same fixes in the order they actually pay off, starting with the thing most sites get wrong first: images.

Developer Tools

Favicon Generator: The Icon Files a Site Actually Needs

The favicon is the one image on your site nobody checks at the size everybody sees. Here is the short list of files a site still needs, the two measurements that decide whether your mark survives a browser tab and an Android mask, and exactly where the WordPress site icon stops.

SEO & Structured Data

WordPress Image SEO: What Works and What Is Folklore

Most image SEO checklists are ordered by how easy each item is to write about. This one ranks the advice by mechanism instead, naming the core function behind every claim, and says plainly which parts are folklore.

Download the free WunderPaint Plugin for WordPress

The WunderPaint workspace with the layers panel, adjustment sliders, text style presets and the asset library along the bottom

The Image Editor & Design Studio

Everything described here can be done in the browser, on your own site. The live demo runs the full editor with nothing to install.

Free

Chaos Art

Autonomous painters make one-of-a-kind abstract art in 3D space - gestures, art movements, painterly media, and embeds that paint a new original for every visitor.

Pro

Particle Strokes

Paint with swarms of light: twenty-two movements, a stamp you draw yourself, and curves that give a stroke a shape - the swarm keeps painting for a few seconds after you let go.

Pro

City Diorama

Any place on earth as a miniature you could hold: real streets, water and building footprints raised into a 3D diorama - or wrapped around a sphere as your own tiny planet.

Free

Papercut Art

Layered paper pictures with real depth - a photo sliced along its actual depth into up to twenty layers, parametric landscapes, animals and clouds you can shape, letters with real counters, and a look that runs on three dials.

Pro

3D Earth Studio

A hyperrealistic globe - day and night with city lights, live clouds, atmosphere halo, country borders and highlights, click-to-place markers with flight-route arcs, satellite orbits, seamless rotation video and a live website embed.

Free

Mystic Studio

Turn a birth date into wall art - a real natal chart with houses and aspects, the moon of that night, zodiac and Chinese zodiac posters, numerology cards and a synastry wheel for two, in eight artful themes.

Free

Marble Bath

Marble paper on a virtual water bath - drop, rake and comb real Ebru patterns with gestures, flowers and classic recipes, razor-sharp at any size and re-editable as a layer.

Free

Day Ring

Turn a day into a beautiful circular schedule - colour-coded time blocks as arcs around a 24-hour clock, with concentric rings for overlaps, emoji, templates and a legend.

Free

Code Shot

Turn code into a gorgeous, share-ready image - syntax highlighting, editor themes, window frames and diff highlighting - then drop it into your design as a re-editable layer.

Pro

3D Solar System Studio

Build a date-accurate 3D solar system - real planet positions for any date, photoreal textures and one slider from artistic to true scale - then drop it into your design as an editable layer.

Pro

3D Molecule Studio

Build a real 3D molecule - from a curated library, the periodic table or a SMILES string - then style it, measure it and drop it into your design as an editable layer.

Pro

3D Textile Studio

Drop your design onto cloth that behaves like the material you pick: silk falls soft, felt holds its shape, flag fabric snaps in the wind. Hang it, blow it and drape it, then lay the finished drape back into your document as a picture.

Pro

3D Particle Studio

Point the engine at any layer and it becomes a cloud of particles that keeps its colours, flowing through a sphere, a galaxy or your own outline. Keep the frame you like as a still, or embed the running engine so it keeps moving on your page.

Free

Origami

Put your own picture on the paper and watch that very sheet fold itself into a crane or a box. Every step is a station you can stop at and turn around in 3D, which is exactly where printed diagrams leave you alone.

Pro

3D Flip Studio

A hardcover you can leaf through, a limp magazine, a strewn pile of sheets, a sticker peeling off its backing. The curl is real geometry, so the print never slides across the paper.