How to svg monochrome conversion: behaviour, limits, and troubleshooting
- Step 1Confirm colours are in attributes, not CSS — Open the SVG. Colours must be
fill="…",stroke="…", orstop-color="…"attributes to be converted. Colours instyle="…"or<style>blocks are ignored — re-export with presentation attributes if needed. - Step 2Check colour formats for grayscale — Grayscale only parses hex and eleven named colours (black, white, red, green, blue, yellow, orange, purple, pink, gray, grey). If you see
rgb(),hsl(), or other names, grayscale will leave them — convert to hex first. - Step 3Predict single-mode behaviour — Single mode overwrites every fill/stroke/stop-color value with your target, including
currentColorand named colours, exceptnone,transparent, andurl(...). - Step 4Account for opacity and filters — Opacity attributes and filter colours (
flood-color,lighting-color) are not changed. A semi-transparent fill stays semi-transparent; a coloured drop-shadow stays coloured. - Step 5Check for embedded rasters — An
<image>base64 bitmap can't be recoloured — vector shapes convert, the bitmap stays as-is. Audit for embedded images if the output looks partly colour. - Step 6Verify against the size limit — Free rejects over 5 MB, Pro over 50 MB. Monochrome needs Pro+. Shrink large files with the minifier before converting if you're near the cap.
What gets touched, what doesn't
The converter's exact scope. 'Skipped' values are recognised and preserved on purpose.
| Target | Single mode | Grayscale mode |
|---|---|---|
fill attribute | → target colour | → luminance grey (if parseable) |
stroke attribute | → target colour | → luminance grey (if parseable) |
stop-color attribute | → target colour | → luminance grey (if parseable) |
fill="none" | skipped | skipped |
*="transparent" | skipped | skipped |
*="url(#id)" | skipped | skipped |
style="fill:…" | ignored | ignored |
<style> block colours | ignored | ignored |
opacity / fill-opacity | ignored | ignored |
flood-color / lighting-color | ignored | ignored |
embedded <image> bitmap | ignored | ignored |
Colour formats grayscale can parse
Grayscale must resolve a colour to hex first. Unparseable values are left unchanged. Single mode overwrites all of these regardless.
| Format | Grayscale parses it? | Result in grayscale |
|---|---|---|
#rgb / #rrggbb | Yes | Converted to luminance grey |
| named: black/white/red/green/blue/yellow/orange/purple/pink/gray/grey | Yes | Converted |
other named (tomato, navy, teal…) | No | Unchanged |
rgb() / rgba() | No | Unchanged |
hsl() / hsla() | No | Unchanged |
currentColor | No | Unchanged |
Worked grayscale examples (BT.601)
L = round(0.299R + 0.587G + 0.114B), then #LLLLLL. Computed from the actual formula.
| Input hex | R,G,B | L (rounded) | Output grey |
|---|---|---|---|
#ff0000 | 255,0,0 | 76 (0x4c) | #4c4c4c |
#00ff00 | 0,255,0 | 150 (0x96) | #969696 |
#0000ff | 0,0,255 | 29 (0x1d) | #1d1d1d |
#ffffff | 255,255,255 | 255 | #ffffff |
#808080 | 128,128,128 | 128 (0x80) | #808080 |
Cookbook
Behaviour reference with the formula and the two most common troubleshooting cases.
Compute a grayscale value by hand
Trace the formula so you can predict any output grey.
fill="#e11d48" → R=225 G=29 B=72 L = round(0.299*225 + 0.587*29 + 0.114*72) = round(67.275 + 17.023 + 8.208) = round(92.5) = 93 → 0x5d Output: #5d5d5d
'It didn't all convert' — colour in CSS
The classic case. Attributes convert; CSS colours don't.
<style>.brand{fill:#1d4ed8}</style>
<circle class="brand" .../> ← stays blue
<rect fill="#1d4ed8" .../> ← converts
Fix: export/author colours as attributes, not CSS.'Grayscale did nothing' — unparseable format
rgb()/hsl()/unknown names pass through grayscale untouched.
fill="rgb(29,78,216)" → unchanged in grayscale fill="navy" → unchanged (navy not in list) fill="#1d4ed8" → converts to a grey Fix: normalise to hex, or use single mode.
currentColor: single vs grayscale
The one value that behaves differently across modes.
fill="currentColor" Single (#000000): → #000000 (overwritten) Grayscale: → currentColor (left intact)
Skipped values stay put
none/transparent/url() are preserved in both modes.
fill="none" → none fill="transparent" → transparent fill="url(#grad)" → url(#grad) (only the gradient's own stop-color attrs convert)
Edge cases and what actually happens
Colour defined in style attribute or <style> block
IgnoredThe converter rewrites only the three colour attributes. A style="fill:#f00" or a CSS rule in <style> is not an attribute, so it is left unchanged. This is the #1 cause of 'only some of it converted'. Re-export with presentation attributes, or move colours into attributes first.
Grayscale on rgb()/rgba()/hsl()
Preserved (unchanged)Grayscale resolves a colour to hex before computing the grey; it only understands hex and eleven named colours. rgb(), rgba(), and hsl() aren't parsed, so they're returned unchanged. Single mode overwrites them regardless. Normalise to hex for reliable grayscale.
Named colour outside the supported eleven
Preserved (unchanged)Grayscale recognises only black, white, red, green, blue, yellow, orange, purple, pink, gray, grey. Anything else (navy, tomato, teal, coral…) passes through. Single mode replaces any named colour. If a CSS named colour must grayscale, convert it to hex first.
currentColor
Mode-dependentSingle mode overwrites currentColor with the target (it's not in the skip set), which silently breaks inheritance. Grayscale leaves it because it isn't parseable to hex. For currentColor-based theming, use the CSS variable injector or SVG to Tailwind rather than this tool.
Opacity attributes unchanged
Ignoredopacity, fill-opacity, and stroke-opacity are not colour attributes, so a 50% fill stays 50% after conversion. If transparency was affecting how a colour read, the converted grey reflects the colour at full opacity — the alpha is applied separately at render time and is untouched.
Filter colours (flood-color, lighting-color)
IgnoredColours inside filter primitives aren't among the three rewritten attributes, so a coloured glow or drop-shadow keeps its colour after conversion. Edit filters manually or via the filter injector if they must match.
Embedded raster image
Preserved (unchanged)A base64 PNG/JPEG in <image> is pixel data, not a vector attribute, and can't be recoloured — it stays full colour while vector shapes convert. Convert the bitmap outside this tool if needed.
Gradient with a url() reference
Preserved (unchanged) referencefill="url(#grad)" is skipped (it points to a paint server, not a colour), but the gradient's own <stop stop-color> attributes are converted. So the gradient still renders, in grey or your single colour — the reference and structure are intact by design.
Element with no explicit fill
Preserved (unchanged)If an element has no fill attribute it inherits SVG's default (black). The converter only edits attributes that exist, so it adds none. The element already renders black, so single-black and grayscale look right; add an explicit attribute if you need a different target on it.
File over the tier size limit
RejectedFree rejects SVGs over 5 MB; Pro over 50 MB; monochrome requires Pro or higher. This usually only triggers on embedded-raster or auto-traced files — run the minifier first.
Frequently asked questions
Exactly which parts of my SVG does it change?
Only the values of fill, stroke, and stop-color attributes. Everything else — geometry, IDs, viewBox, opacity, filters, CSS, embedded rasters — is left exactly as it was.
What happens to fill='none'?
It's skipped and preserved. none means 'no paint', not a colour, so converting it would fill shapes you meant to leave hollow. Both modes leave none untouched.
What happens to currentColor?
In single mode it's overwritten with your target colour (breaking inheritance); in grayscale it's left unchanged because it can't be parsed to a hex value. If you rely on currentColor, don't use single mode over it.
Why didn't grayscale change my rgb() colours?
Grayscale parses only hex and eleven named colours. rgb(), rgba(), and hsl() aren't parsed and pass through unchanged. Convert them to hex first, or use single mode, which overwrites any format.
Which named colours does grayscale understand?
Eleven: black, white, red, green, blue, yellow, orange, purple, pink, gray, grey. Other CSS names (navy, tomato, etc.) are not converted in grayscale. Single mode handles any named colour.
What grayscale formula is used?
BT.601 luma: L = round(0.299R + 0.587G + 0.114B), output as #LLLLLL. It's a per-colour, perceptually weighted conversion, which is why blue ends up darkest.
Does it preserve gradients?
Yes. The url(#id) reference is skipped, but the gradient's <stop stop-color> attributes are converted, so the gradient still renders with smooth transitions in grey or your chosen colour.
Will it flatten opacity or transparency?
No. Opacity attributes aren't colour attributes and aren't touched. A semi-transparent fill stays semi-transparent after conversion. Flatten transparency separately if you need it.
Can it convert SVGs with embedded raster images?
It converts the vector elements but cannot recolour embedded raster (base64 PNG/JPEG) — that data is left unchanged. Handle the bitmap outside this tool.
Does it touch colours in <style> blocks or style attributes?
No. Only the fill/stroke/stop-color attributes are rewritten. CSS colours are ignored. This is the most common reason a file 'only partly' converts.
What's the file-size limit?
Free: 5 MB (but monochrome requires Pro). Pro: 50 MB with up to 20 files per batch. Pro + Media: 200 MB / 100 files. Developer: 2 GB / unlimited.
Is the conversion reversible?
No — once colours are overwritten the originals aren't stored in the output. Keep your source file. If you need themeable recolouring instead of a one-way flatten, use the CSS variable injector or hex swapper.
Privacy first
Every JAD SVG tool runs entirely in your browser using the DOM API and Canvas. Your SVG files never leave your device — verified by zero outbound network requests during processing.