[{"data":1,"prerenderedAt":120},["ShallowReactive",2],{"blog-extract-brand-colors-from-svg-logos":3},{"post":4,"related":51,"previous":97,"next":109},{"slug":5,"title":6,"description":7,"excerpt":8,"date":9,"updated":10,"status":11,"author":12,"authorTitle":10,"category":13,"categoryName":14,"categoryAccent":15,"tags":16,"image":20,"cover":21,"imageAlt":10,"featured":22,"readingMinutes":23,"wordCount":24,"headings":25,"html":48,"canonicalUrl":10,"sourceFile":49,"path":50},"extract-brand-colors-from-svg-logos","How to Extract Brand Colors From SVG Logos","An SVG logo is text, so reading its colors should be trivial. Six things about real-world SVGs make it anything but, starting with currentColor.","An SVG is a text file with the colors written in it. Compared to sampling pixels out of a PNG, reading fill=\"#1F6FEB\" out of some markup should be the easy case. In practice SVG logos are the single…","2026-08-20T09:00:00.000Z","","published","Colorize","engineering","Engineering","#22D3EE",[17,18,19],"brand colors","svg","logos","/social/blog/extract-brand-colors-from-svg-logos.png","/social/blog/extract-brand-colors-from-svg-logos-cover.png",false,6,1299,[26,30,33,36,39,42,45],{"id":27,"text":28,"level":29},"1-currentcolor-renders-as-black","1. currentColor renders as black",2,{"id":31,"text":32,"level":29},"2-gradient-stops-cover-almost-no-pixels","2. Gradient stops cover almost no pixels",{"id":34,"text":35,"level":29},"3-colors-hide-in-five-different-places","3. Colors hide in five different places",{"id":37,"text":38,"level":29},"4-not-every-declared-color-is-a-visible-color","4. Not every declared color is a visible color",{"id":40,"text":41,"level":29},"5-rasterizing-is-full-of-small-traps","5. Rasterizing is full of small traps",{"id":43,"text":44,"level":29},"6-dark-mode-variants-live-in-the-same-file","6. Dark mode variants live in the same file",{"id":46,"text":47,"level":29},"putting-it-together","Putting it together","\u003Cp>An SVG is a text file with the colors written in it. Compared to sampling pixels out of a PNG, reading \u003Ccode>fill=&quot;#1F6FEB&quot;\u003C/code> out of some markup should be the easy case. In practice SVG logos are the single most common source of wrong brand colors, and the reasons are specific and fixable.\u003C/p>\n\u003Cp>Here are the six that account for nearly all of it.\u003C/p>\n\u003Ch2 id=\"1-currentcolor-renders-as-black\">\u003Ca class=\"heading-anchor\" href=\"#1-currentcolor-renders-as-black\" aria-label=\"Link to this section\">#\u003C/a>1. currentColor renders as black\u003C/h2>\n\u003Cp>This is the big one. A large share of modern logos, especially anything that came out of an icon system or a component library, are authored like this:\u003C/p>\n\u003Cdiv class=\"code-block\">\u003Cspan class=\"code-lang\" aria-hidden=\"true\">html\u003C/span>\u003Cpre>\u003Ccode class=\"language-html\">&lt;svg viewBox=&quot;0 0 32 32&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;\n  &lt;path fill=&quot;currentColor&quot; d=&quot;M4 16 L16 4 L28 16 L16 28 Z&quot; /&gt;\n&lt;/svg&gt;\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>\u003Ccode>currentColor\u003C/code> is not a color. It is a reference to the computed \u003Ccode>color\u003C/code> property of the element the SVG is sitting inside. On the live page it inherits the brand color from a CSS rule several levels up the tree. Pull the file out on its own and hand it to a rasterizer, and there is no inherited \u003Ccode>color\u003C/code> to resolve against, so it falls back to the initial value: black.\u003C/p>\n\u003Cp>The result is a monochrome palette, confidently returned, for a logo that is bright blue on screen. It is worse than a failure because it looks like a success.\u003C/p>\n\u003Cp>The fix is to detect it rather than to try to render through it:\u003C/p>\n\u003Cdiv class=\"code-block\">\u003Cspan class=\"code-lang\" aria-hidden=\"true\">js\u003C/span>\u003Cpre>\u003Ccode class=\"language-js\">const isCssDependent = (svg) =&gt;\n  /currentcolor/i.test(svg) ||\n  /fill\\s*=\\s*[&quot;']inherit[&quot;']/i.test(svg) ||\n  /\\bfill\\s*=\\s*[&quot;']\\s*[&quot;']/i.test(svg)\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Once detected you have two honest options. Skip that candidate for color purposes and fall back to another asset, such as the favicon or a raster version of the same mark. Or resolve the color from the live page, by reading the computed \u003Ccode>color\u003C/code> of the element the SVG was found in before you detach it. What you must not do is rasterize it and report the result.\u003C/p>\n\u003Caside class=\"callout\">\n  \u003Cstrong>Detach and re-attach are different problems.\u003C/strong> The same SVG is\n  still perfectly good as an asset to store and serve. It is only unusable as a\n  color source. Keep the two decisions separate or you will start discarding\n  good logos.\n\u003C/aside>\n\u003Ch2 id=\"2-gradient-stops-cover-almost-no-pixels\">\u003Ca class=\"heading-anchor\" href=\"#2-gradient-stops-cover-almost-no-pixels\" aria-label=\"Link to this section\">#\u003C/a>2. Gradient stops cover almost no pixels\u003C/h2>\n\u003Cp>Gradients are declared once in \u003Ccode>&lt;defs&gt;\u003C/code> and referenced by ID:\u003C/p>\n\u003Cdiv class=\"code-block\">\u003Cspan class=\"code-lang\" aria-hidden=\"true\">html\u003C/span>\u003Cpre>\u003Ccode class=\"language-html\">&lt;defs&gt;\n  &lt;linearGradient id=&quot;g&quot;&gt;\n    &lt;stop offset=&quot;0%&quot; stop-color=&quot;#7C3AED&quot; /&gt;\n    &lt;stop offset=&quot;100%&quot; stop-color=&quot;#22D3EE&quot; /&gt;\n  &lt;/linearGradient&gt;\n&lt;/defs&gt;\n&lt;circle cx=&quot;16&quot; cy=&quot;16&quot; r=&quot;12&quot; fill=&quot;url(#g)&quot; /&gt;\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>The two colors a designer would name are \u003Ccode>#7C3AED\u003C/code> and \u003Ccode>#22D3EE\u003C/code>. Rasterize this and count pixels, and neither of them wins anything. Every pixel in that circle is a different interpolated blend, the endpoints exist on a single row each, and the most frequent value is some purple-cyan midpoint that appears in no brand guide anywhere.\u003C/p>\n\u003Cp>Pixel frequency analysis structurally cannot find a gradient's endpoints. The markup can, trivially, which is the argument for parsing the text as well as rasterizing rather than choosing between them.\u003C/p>\n\u003Ch2 id=\"3-colors-hide-in-five-different-places\">\u003Ca class=\"heading-anchor\" href=\"#3-colors-hide-in-five-different-places\" aria-label=\"Link to this section\">#\u003C/a>3. Colors hide in five different places\u003C/h2>\n\u003Cp>If you are scanning markup, scanning for \u003Ccode>fill=\u003C/code> is not enough. Colors in real SVGs appear as:\u003C/p>\n\u003Cul>\u003Cli>Presentation attributes: \u003Ccode>fill\u003C/code>, \u003Ccode>stroke\u003C/code>, \u003Ccode>stop-color\u003C/code>, \u003Ccode>flood-color\u003C/code>, \u003Ccode>lighting-color\u003C/code>\u003C/li>\u003Cli>Inline styles: \u003Ccode>style=&quot;fill: #1F6FEB; stroke: rgb(0 0 0)&quot;\u003C/code>\u003C/li>\u003Cli>A \u003Ccode>&lt;style&gt;\u003C/code> block inside the SVG, with class selectors applied to elements\u003C/li>\u003Cli>CSS custom properties defined in that same block and referenced by \u003Ccode>var()\u003C/code>\u003C/li>\u003Cli>Nested \u003Ccode>&lt;svg&gt;\u003C/code> or \u003Ccode>&lt;image&gt;\u003C/code> elements carrying a raster payload as a data URI\u003C/li>\u003C/ul>\n\u003Cp>The first two are a straightforward pair of regexes over attribute and style forms. The third and fourth need at least a shallow understanding of the embedded stylesheet. The fifth is a raster image wearing an SVG hat, and the only way to read it is to decode the data URI and treat it as the image it is.\u003C/p>\n\u003Cp>Whatever you scan for, you also need to reject the values that are not colors: \u003Ccode>none\u003C/code>, \u003Ccode>transparent\u003C/code>, \u003Ccode>inherit\u003C/code>, \u003Ccode>currentColor\u003C/code>, and anything starting with \u003Ccode>url(\u003C/code>, which is a paint server reference rather than a color.\u003C/p>\n\u003Ch2 id=\"4-not-every-declared-color-is-a-visible-color\">\u003Ca class=\"heading-anchor\" href=\"#4-not-every-declared-color-is-a-visible-color\" aria-label=\"Link to this section\">#\u003C/a>4. Not every declared color is a visible color\u003C/h2>\n\u003Cp>This is where naive markup parsing goes wrong in the opposite direction from naive rasterization. These elements all carry fills that never reach a viewer:\u003C/p>\n\u003Cdiv class=\"code-block\">\u003Cspan class=\"code-lang\" aria-hidden=\"true\">html\u003C/span>\u003Cpre>\u003Ccode class=\"language-html\">&lt;mask id=&quot;m&quot;&gt;&lt;rect fill=&quot;#FFFFFF&quot; width=&quot;32&quot; height=&quot;32&quot; /&gt;&lt;/mask&gt;\n&lt;clipPath id=&quot;c&quot;&gt;&lt;path fill=&quot;#FF0000&quot; d=&quot;...&quot; /&gt;&lt;/clipPath&gt;\n&lt;path fill=&quot;#00FF00&quot; opacity=&quot;0&quot; d=&quot;...&quot; /&gt;\n&lt;g display=&quot;none&quot;&gt;&lt;path fill=&quot;#FF00FF&quot; d=&quot;...&quot; /&gt;&lt;/g&gt;\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Inside a \u003Ccode>&lt;mask&gt;\u003C/code>, color is not color, it is a luminance channel controlling transparency. The white rectangle is saying &quot;show everything&quot;, not &quot;the brand is white&quot;. A \u003Ccode>&lt;clipPath&gt;\u003C/code> child's fill is ignored entirely; only its geometry is used. And elements at zero opacity or \u003Ccode>display: none\u003C/code> are frequently leftovers from the designer's working file, exported and forgotten.\u003C/p>\n\u003Cp>A markup pass that counts all of these hands back colors from a file that never displayed them. This is the strongest argument for reconciling the two methods: markup parsing finds colors that rasterizing misses, and rasterizing confirms that a color parsed from markup actually appears on screen. Colors found by both are the ones you trust most.\u003C/p>\n\u003Ch2 id=\"5-rasterizing-is-full-of-small-traps\">\u003Ca class=\"heading-anchor\" href=\"#5-rasterizing-is-full-of-small-traps\" aria-label=\"Link to this section\">#\u003C/a>5. Rasterizing is full of small traps\u003C/h2>\n\u003Cp>When you do rasterize, several things silently produce a blank or wrong image.\u003C/p>\n\u003Cp>\u003Cstrong>Missing namespace.\u003C/strong> Markup lifted out of a page with \u003Ccode>outerHTML\u003C/code> often has no \u003Ccode>xmlns\u003C/code> attribute, because the HTML parser supplied the SVG namespace implicitly and it was never serialized. Almost every standalone rasterizer treats a namespace free \u003Ccode>&lt;svg&gt;\u003C/code> as an unknown element and returns an empty canvas. Add it back before handing the string over:\u003C/p>\n\u003Cdiv class=\"code-block\">\u003Cspan class=\"code-lang\" aria-hidden=\"true\">js\u003C/span>\u003Cpre>\u003Ccode class=\"language-js\">const ensureNamespace = (svg) =&gt;\n  /\\sxmlns\\s*=/.test(svg)\n    ? svg\n    : svg.replace(/^&lt;svg\\b/i, '&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot;')\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>\u003Cstrong>No intrinsic size.\u003C/strong> An SVG with only a \u003Ccode>viewBox\u003C/code> and no \u003Ccode>width\u003C/code> or \u003Ccode>height\u003C/code> has no intrinsic dimensions. Rasterizers disagree about what to do: some default to 100x100, some to the viewBox, some fail. Set an explicit render size instead of finding out which one you have.\u003C/p>\n\u003Cp>\u003Cstrong>Renderer differences.\u003C/strong> librsvg, resvg and a headless browser do not implement the same subset. Filters, \u003Ccode>&lt;foreignObject&gt;\u003C/code> and some blend modes are the usual disagreements. If you rasterize server side and compare against what a browser shows, expect the mismatches to cluster in those features.\u003C/p>\n\u003Cp>\u003Cstrong>External references.\u003C/strong> \u003Ccode>&lt;use href=&quot;sprite.svg#logo&quot;&gt;\u003C/code> and \u003Ccode>&lt;image href=&quot;...&quot;&gt;\u003C/code> point outside the file. Detached from its origin those resolve to nothing, and you get a correctly rendered picture of an empty box.\u003C/p>\n\u003Ch2 id=\"6-dark-mode-variants-live-in-the-same-file\">\u003Ca class=\"heading-anchor\" href=\"#6-dark-mode-variants-live-in-the-same-file\" aria-label=\"Link to this section\">#\u003C/a>6. Dark mode variants live in the same file\u003C/h2>\n\u003Cp>The neat trick of shipping one logo that adapts to the page theme is now common, and it means a single file contains two palettes:\u003C/p>\n\u003Cdiv class=\"code-block\">\u003Cspan class=\"code-lang\" aria-hidden=\"true\">html\u003C/span>\u003Cpre>\u003Ccode class=\"language-html\">&lt;style&gt;\n  .mark { fill: #0B1220; }\n  @media (prefers-color-scheme: dark) {\n    .mark { fill: #F8FAFC; }\n  }\n&lt;/style&gt;\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Both fills are real brand colors in the sense that both get shown to real users. Which one you extract depends entirely on the color scheme your renderer emulates, and most default to light without telling you. If the distinction matters for your use case, emulate both and keep the results labelled rather than merging them into one undifferentiated list.\u003C/p>\n\u003Ch2 id=\"putting-it-together\">\u003Ca class=\"heading-anchor\" href=\"#putting-it-together\" aria-label=\"Link to this section\">#\u003C/a>Putting it together\u003C/h2>\n\u003Cp>The approach that survives all six is not markup parsing or rasterization but both, with the results reconciled:\u003C/p>\n\u003Col>\u003Cli>\u003Cstrong>Screen the file first.\u003C/strong> If it depends on inherited color, do not use it as a color source. Resolve from the page context or move to the next candidate.\u003C/li>\u003Cli>\u003Cstrong>Parse the markup\u003C/strong> for attributes, inline styles and gradient stops, skipping anything inside masks, clip paths, hidden elements and paint server references.\u003C/li>\u003Cli>\u003Cstrong>Rasterize at a fixed size\u003C/strong> with the namespace repaired, and count pixel frequencies while ignoring transparent pixels.\u003C/li>\u003Cli>\u003Cstrong>Reconcile.\u003C/strong> Markup supplies the gradient endpoints and small accents that pixel counting cannot see. Pixels supply the evidence of what is actually visible and in what proportion.\u003C/li>\u003Cli>\u003Cstrong>Deduplicate perceptually.\u003C/strong> Both passes produce near-identical variants of the same color, and both feed the same cluster. Merge them in a perceptually uniform space rather than in RGB, for the reasons in \u003Ca href=\"/blog/accurate-brand-color-palettes\">The Complete Guide to Accurate Brand Color Palettes\u003C/a>.\u003C/li>\u003C/ol>\n\u003Cp>A note on hex parsing while you are writing the markup pass: SVG accepts four hex forms, \u003Ccode>#RGB\u003C/code>, \u003Ccode>#RGBA\u003C/code>, \u003Ccode>#RRGGBB\u003C/code> and \u003Ccode>#RRGGBBAA\u003C/code>, plus \u003Ccode>rgb()\u003C/code>, \u003Ccode>hsl()\u003C/code>, their alpha variants, and the 148 CSS named colors. A regex that only handles six digit hex will quietly skip a meaningful fraction of real logos, and \u003Ccode>#0FA\u003C/code> is not an unusual thing to find in a hand-optimized file.\u003C/p>\n\u003Cp>The payoff for getting this right is that SVG stops being the awkward case and becomes the best one. It is the only format where the brand's actual chosen values are present as declared numbers rather than inferred from a grid of pixels. You can see the results on real companies in the \u003Ca href=\"/brand-colors\">brand color directory\u003C/a>.\u003C/p>","content/blog/extract-brand-colors-from-svg-logos.md","/blog/extract-brand-colors-from-svg-logos",[52,66,83],{"slug":53,"title":54,"description":55,"excerpt":56,"date":57,"updated":10,"status":11,"author":12,"authorTitle":10,"category":58,"categoryName":59,"categoryAccent":60,"tags":61,"image":63,"cover":64,"imageAlt":10,"featured":22,"readingMinutes":23,"path":65},"extract-dominant-colors-from-a-logo","The Best Way to Extract Dominant Colors From a Logo","Median cut, octree and k-means were built to compress photographs. Point them at a logo and they return three whites and a gray. Here is what works instead.","Reach for a color extraction library and you will almost certainly get median cut, usually through ColorThief or one of its ports. Feed it a photograph and it does a genuinely good job. Feed it a logo…","2026-08-22T09:00:00.000Z","color-theory","Color Theory","#E879F9",[17,19,62],"color theory","/social/blog/extract-dominant-colors-from-a-logo.png","/social/blog/extract-dominant-colors-from-a-logo-cover.png","/blog/extract-dominant-colors-from-a-logo",{"slug":67,"title":68,"description":69,"excerpt":70,"date":71,"updated":10,"status":11,"author":12,"authorTitle":10,"category":72,"categoryName":73,"categoryAccent":74,"tags":75,"image":78,"cover":79,"imageAlt":10,"featured":80,"readingMinutes":81,"path":82},"how-to-extract-brand-colors-from-any-website","How to Extract Brand Colors From Any Website","Four ways to pull the exact hex codes off a live site (DevTools, the CSS, a screenshot, and an extractor) and which one is right for each job.","You need the exact colors a site uses, and you need them as hex codes rather than as a hunch. Maybe you're building a pitch deck for a client, matching an integration to a partner's brand, or…","2026-08-23T09:00:00.000Z","guides","Guides","#818CF8",[17,76,77],"workflow","css","/social/blog/how-to-extract-brand-colors-from-any-website.png","/social/blog/how-to-extract-brand-colors-from-any-website-cover.png",true,4,"/blog/how-to-extract-brand-colors-from-any-website",{"slug":84,"title":85,"description":86,"excerpt":87,"date":57,"updated":71,"status":11,"author":12,"authorTitle":10,"category":88,"categoryName":89,"categoryAccent":90,"tags":91,"image":93,"cover":94,"imageAlt":10,"featured":22,"readingMinutes":95,"path":96},"colorize-vs-site-palette","Colorize vs Site Palette: Which Extractor to Use","Both tools pull colors off a live site. What differs is where you run them, what counts as a palette, and what you can take away.","Site Palette and Colorize solve the same sentence (\"get me the colors this website uses\") from opposite ends. Site Palette is a browser extension: you sign in, install it, and it quantizes what is…","comparisons","Comparisons","#34D399",[17,76,92],"comparison","/social/blog/colorize-vs-site-palette.png","/social/blog/colorize-vs-site-palette-cover.png",9,"/blog/colorize-vs-site-palette",{"slug":98,"title":99,"description":100,"excerpt":101,"date":102,"updated":10,"status":11,"author":12,"authorTitle":10,"category":58,"categoryName":59,"categoryAccent":60,"tags":103,"image":105,"cover":106,"imageAlt":10,"featured":22,"readingMinutes":107,"path":108},"accurate-brand-color-palettes","The Complete Guide to Accurate Brand Color Palettes","Near-duplicate shades are the most common flaw in an extracted palette. Here is how CIELAB and delta E collapse them into the colors a brand actually uses.","You point an extractor at a logo, ask for the brand palette, and get five colors back. Three of them are the same blue.","2026-08-19T09:00:00.000Z",[62,17,104],"palettes","/social/blog/accurate-brand-color-palettes.png","/social/blog/accurate-brand-color-palettes-cover.png",7,"/blog/accurate-brand-color-palettes",{"slug":110,"title":111,"description":112,"excerpt":113,"date":114,"updated":10,"status":11,"author":12,"authorTitle":10,"category":13,"categoryName":14,"categoryAccent":15,"tags":115,"image":116,"cover":117,"imageAlt":10,"featured":22,"readingMinutes":118,"path":119},"extract-brand-colors-from-modern-css","How to Extract Brand Colors From Modern CSS","Regex over stylesheets used to be a fine way to collect a site's colors. CSS Color 4 and 5 ended that. Here is what to read instead of source text.","For about fifteen years you could collect every color a website used with three regular expressions. One for hex, one for rgb() and rgba(), one for hsl() and hsla(). Between them they matched…","2026-08-21T09:00:00.000Z",[77,17,62],"/social/blog/extract-brand-colors-from-modern-css.png","/social/blog/extract-brand-colors-from-modern-css-cover.png",5,"/blog/extract-brand-colors-from-modern-css",1787496473241]