Tag: color

Finally, it Will Be Easy to Change the Color of List Bullets

In my germinating years, the general advice was this:

<ul>   <li><span>List item</span></li>   <!-- ... --> </ul>
li { color: red; } /* bullet */ li span (color: black; } /* text */

Not terrible, but not great. You’re “resetting” everything at the span level, so it gets more complicated the more you do.

Things are getting much easier. Let’s take a walk through this world getting more modern as we go.


An alternative was to rip off the default list styling and replace it with a pseudo-element.

ul {   list-style: none; }  li::before {   content: "• ";   color: red; }

If we need to count, we could do that with CSS counters.

ol {   list-style: none;   counter-reset: my-awesome-counter; }  ol li {   counter-increment: my-awesome-counter; }  ol li::before {   content: counter(my-awesome-counter) ". ";   color: red; }

Quick aside here: this doesn’t help with the color, but you can specify what character to use for the bullet by setting a string, like:

ul {   list-style-type: '✽ '; }

This is as of Firefox 39 (2015) and Chrome 79 (which comes out Dec 9, 2019).

For ordered lists, there is a ton of language-specific options. And those language styles work for CSS counters as well, which you can learn more about in Hui Jing’s deep dive.

See the Pen
Random CSS counters playground
by Chen Hui Jing (@huijing)
on CodePen.


But all the while, we only wanted to select the stupid bullet (or whatever it is) and style it. Now we are starting to be able to do just that.

As of Firefox 68 (July 2019), you can do like:

li::marker {   color: red;   content: "►"; }

…which, as you can see, changes the color and the bullet thing That is definitely the cleanest and easiest way to go, so it’s nice to see progress.

Tejas demonstrates:

See the Pen
::marker example
by Tejas (@tejask)
on CodePen.

Manuel Matuzović notes that if you set an element to a list-item display type, you can use markers on them as well.

h2 {   display: list-item; }  h2::marker {   color: orange;   content: "☞"; }

Even Safari has support at the time of this writing, so we should lean on Chrome here.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Opera Firefox IE Edge Safari
No No 68 No No 11.1

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
11.3-11.4 No No No No No

The post Finally, it Will Be Easy to Change the Color of List Bullets appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,

Designing accessible color systems

The team at Stripe explores how they’re refining their color palette to make it more accessible and legible for users across all their products and interfaces. Not only that but the team built a wonderful and yet entirely bonkers app for figuring out the ideal range of colors that they needed.

We built a web interface to allow us to visualize and manipulate our color system using perceptually uniform color models. The tool gave us an immediate feedback loop while we were iterating on our colors—we could see the effect of every change.

This tool is…whoa! I would love to learn a bit more about why they built this though as it looks like it wouldn’t have been a particularly quick and easy thing to put together. I wonder if that team has to support a wide-range of colors for their charts or data-visualization UI (as complex charts can often require a much larger range of colors for comparing bits of data effectively). Either way, this is pretty inspiring work.

This somewhat harkens to a couple of techniques for enforcing accessible colors, including one that uses custom properties with calc() and rgb by Josh Bader, and another by Facundo Corradini that also uses custom properties but with hsl with conditional statements.

Direct Link to ArticlePermalink

The post Designing accessible color systems appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Color Inputs: A Deep Dive into Cross-Browser Differences

In this article, we’ll be taking a look at the structure inside <input type='color'> elements, browser inconsistencies, why they look a certain way in a certain browser, and how to dig into it. Having a good understanding of this input allows us to evaluate whether a certain cross-browser look can be achieved and how to do so with a minimum amount of effort and code.

Here’s exactly what we’re talking about:

But before we dive into this, we need to get into…

Accessibility issues!

We’ve got a huge problem here: for those who completely rely on a keyboard, this input doesn’t work as it should in Safari and in Firefox on Windows, but it does work in Firefox on Mac and Linux (which I only tested on Fedora, so feel free to yell at me in the comments if it doesn’t work for you using another distribution).

In Firefox on Windows, we can Tab to the input to focus it, press Enter to bring up a dialog… which we then cannot navigate with the keyboard!

I’ve tried tabbing, arrow keys, and every other key available on the keyboard… nothing! I could at least close the dialog with good old Alt + F4. Later, in the bug ticket I found for this on Bugzilla, I also discovered a workaround: Alt + Tab to another window, then Alt + Tab back and the picker dialog can be navigated with the keyboard.

Things are even worse in Safari. The input isn’t even focusable (bug ticket) if VoiceOver isn’t on. And even when using VoiceOver, tabbing through the dialog the inputs opens is impossible.

If you’d like to use <input type='color'> on an actual website, please let browsers know this is something that needs to be solved!

How to look inside

In Chrome, we need to bring up DevTools, go to Settings and, in the Preferences section under Elements, check the Show user agent shadow DOM option.

How to view the structure inside an input in Chrome.

Then, when we return to inspect our element, we can see inside its shadow DOM.

In Firefox, we need to go to about:config and ensure the devtools.inspector.showAllAnonymousContent flag is set to true.

How to view the structure inside an input in Firefox.

Then, we close the DevTools and, when we inspect our input again, we can see inside our input.

Sadly, we don’t seem to have an option for this in pre-Chromium Edge.

The structure inside

The structure revealed in DevTools differs from browser to browser, just like it does for range inputs.

In Chrome, at the top of the shadow DOM, we have a <div> wrapper that we can access using ::-webkit-color-swatch-wrapper.

Inside it, we have another <div> we can access with ::-webkit-color-swatch.

Screenshot of Chrome DevTools showing the shadow DOM of the <input type='color'>. Right at the top, we have a div which is the swatch wrapper and can be accessed using ::-webkit-color-swatch-wrapper. Inside it, there’s another div which is the swatch and can be accessed using ::-webkit-color-swatch. This div has the background-color set to the value of the parent color input.”/><figcaption>Inner structure in Chrome.</figcaption></figure>
<p>In Firefox, we only see one <code><div></code>, but it’s not labeled in any way, so how do we access it?</p>
<p>On a hunch, given this <code><div></code> has the <code>background-color</code> set to the input’s <code>value</code> attribute, just like the <code>::-webkit-color-swatch</code> component, I tried <code>::-moz-color-swatch</code>. And it turns out it works!</p>
<figure><img src=this issue from 2016. Pre-Chromium Edge apparently doesn’t allow us to style whatever is inside this input. Well, that’s a bummer.

How to look at the browser styles

In all browsers, we have the option of not applying any styles of our own and then looking at the computed styles.

In Chrome and Firefox, we can also see the user agent stylesheet rule sets that are affecting the currently selected element (though we need to explicitly enable this in Firefox, as seen in the previous section).

Screenshot collage of Chrome DevTools and Firefox DevTools showing where to look for user agent styles: Elements > Styles in Chrome and Inspector > Styles in Firefox.”/><figcaption>Checking browser styles in Chrome and Firefox.</figcaption></figure>
<p>This is oftentimes more helpful than the computed styles, but there are exceptions and we should still always check the computed values as well.</p>
<p>In Firefox, we can also see the CSS file for the <code>form</code> elements at <code>view-source:resource://gre-resources/forms.css</code>.</p>
<figure><img src=
Checking browser styles in Firefox.

The input element itself

We’ll now be taking a look at the default values of a few properties in various browsers in order to get a clear picture of what we’d really need to set explicitly in order to get a custom cross-browser result.

The first property I always think about checking when it comes to <input> elements is box-sizing. The initial value of this property is border-box in Firefox, but content-box in Chrome and Edge.

Comparative screenshots of DevTools in the three browsers showing the computed values of box-sizing for the actual input.
The box-sizing values for <input type='color'> compared in Chrome, Firefox and Edge (from top-to-bottom).

We can see that Firefox is setting it to border-box on <input type='color'>, but it looks like Chrome isn’t setting it at all, so it’s left with the initial value of content-box (and I suspect the same is true for Edge).

In any event, what it all means is that, if we are to have a border or a padding on this element, we also need to explicitly set box-sizing so that we get a consistent result across all these browsers.

The font property value is different for every browser, but since we don’t have text inside this input, all we really care about is the font-size, which is consistent across all browsers I’ve checked: 13.33(33)px. This is a value that really looks like it came from dividing 40px by 3, at least in Chrome.

Comparative screenshots of DevTools in the three browsers showing the font values for the actual input.
The font values for <input type='color'> compared in Chrome, Firefox and Edge (from top-to-bottom).

This is a situation where the computed styles are more useful for Firefox, because if we look at the browser styles, we don’t get much in terms of useful information:

Screenshot of what we get if we look at the browser styles where the font was set for Firefox. The value for the font is -moz-field, which is an alias for the look of a native text field. Expanding this to check the longhands shows us empty instead of actual values.
Sometimes the browser styles are pretty much useless (Firefox screenshot).

The margin is also consistent across all these browsers, computing to 0.

Comparative screenshots of DevTools in the three browsers showing the margin values for the actual input.
The margin values for <input type='color'> compared in Chrome, Firefox and Edge (from top-to-bottom).

The border is different for every single browser. In both Chrome and Edge, we have a solid 1px one, but the border-color is different (rgb(169, 169, 169) for Chrome and rgb(112, 112, 112) for Edge). In Firefox, the border is an outset 2px one, with a border-color of… ThreeDLightShadow?!

Comparative screenshots of DevTools in the three browsers showing the border values for the actual input.
The border values for <input type='color'> compared in Chrome, Firefox and Edge (from top-to-bottom).

What’s the deal with ThreeDLightShadow? If it doesn’t sound familiar, don’t worry! It’s a (now deprecated) CSS2 system value, which Firefox on Windows shows me to be rgb(227, 227, 227) in the Computed styles tab.

Screenshot of Computed panel search in Firefox on Windows, showing that the ThreeDLightShadow keyword computes to rgb(227, 227, 227).
Computed border-color for <input type='color'> in Firefox on Windows.

Note that in Firefox (at least on Windows), the operating system zoom level (SettingsSystemDisplayScale and LayoutChange the size of text, apps and other items) is going to influence the computed value of the border-width, even though this doesn’t seem to happen for any other property I’ve checked and it seems to be partially related to the border-style.

Screenshot showing the Windows display settings window with the zoom level options dropdown opened.
Zoom level options on Windows.

The strangest thing is the computed border-width values for various zoom levels don’t seem to make any sense. If we keep the initial border-style: outset, we have:

  • 1.6px for 125%
  • 2px for 150%
  • 1.7px for 175%
  • 1.5px for 200%
  • 1.8px for 225%
  • 1.6px for 250%
  • 1.66667px for 300%

If we set border-style: solid, we have a computed border-width of 2px, exactly as it was set, for zoom values that are multiples of 50% and the exact same computed values as for border-style: outset for all the other zoom levels.

The padding is the same for Chrome and Edge (1px 2px), while Firefox is the odd one out again.

Comparative screenshots of DevTools in the three browsers showing the padding values for the actual input.
The padding values for <input type='color'> compared in Chrome, Firefox and Edge (from top-to-bottom).

It may look like the Firefox padding is 1px. That’s what it is set to and there’s no indication of anything overriding it — if a property is overridden, then it’s shown as grey and with a strike-through.

Screenshot of Firefox DevTools highlighting how the border set on input[type='color'] overrides the one set on input and the look (grey + strike-through) of overridden properties.
Spotting overrides in Firefox.

But the computed value is actually 0 8px! Moreover, this is a value that doesn’t depend on the operating system zoom level. So, what the hairy heck is going on?!

Screenshot of Firefox DevTools showing how the computed padding value on <input type='color'> isn’t the one that was set on input, even if no override seems to be happening.”/><figcaption>Computed value for <code>padding</code> in Firefox doesn’t match the value that was set on input.</figcaption></figure>
<p>Now, if you’ve actually tried inspecting a color input, took a close look at the styles set on it, and your brain works differently than mine (meaning you do read what’s in front of you and don’t just scan for the one thing that interests you, completely ignoring everything else…) then you’ve probably noticed there is something overriding the <code>1px</code> padding (and <a href=should be marked as such) — the flow-relative padding!

Screenshot of Firefox DevTools showing the flow-relative padding overriding the old padding due to higher specificity of selector (input[type='color'] vs. input).
Flow-relative padding overrides in Firefox.

Dang, who knew those properties with lots of letters were actually relevant? Thanks to Zoltan for noticing and letting me know. Otherwise, it probably would have taken me two more days to figure this one out.

This raises the question of whether the same kind of override couldn’t happen in other browsers and/or for other properties.

Edge doesn’t support CSS logical properties, so the answer is a “no” in that corner.

In Chrome, none of the logical properties for margin, border or padding are set explicitly for <input type='color'>, so we have no override.

Concerning other properties in Firefox, we could have found ourselves in the same situation for margin or for border, but with these two, it just so happens the flow-relative properties haven’t been explicitly set for our input, so again, there’s no override.

Even so, it’s definitely something to watch out for in the future!

Moving on to dimensions, our input’s width is 44px in Chrome and Edge and 64px in Firefox.

Comparative screenshots of DevTools in the three browsers showing the width values for the actual input.
The width values for <input type='color'> compared in Chrome, Firefox and Edge (from top-to-bottom).

Its height is 23px in all three browsers.

Comparative screenshots of DevTools in the three browsers showing the height values for the actual input.
The height values for <input type='color'> compared in Chrome, Firefox and Edge (from top-to-bottom).

Note that, since Chrome and Edge have a box-sizing of content-box, their width and height values do not include the padding or border. However, since Firefox has box-sizing set to border-box, its dimensions include the padding and border.

Comparative screenshots of DevTools in the three browsers showing the layout boxes.
The layout boxes for <input type='color'> compared in Chrome, Firefox and Edge (from top-to-bottom).

This means the content-box is 44pxx23px in Chrome and Edge and 44xpxx19px in Firefox, the padding-box is 48pxx25 in Chrome and Edge and 60pxx19px in Firefox and the border-box is 50pxx27px in Chrome and Edge and 64pxx23 in Firefox.

We can clearly see how the dimensions were set in Chrome and I’d assume they were set in the same direct way in Edge as well, even if Edge doesn’t allow us to trace this stuff. Firefox doesn’t show these dimensions as having been explicitly set and doesn’t even allow us to trace where they came from in the Computed tab (as it does for other properties like border, for example). But if we look at all the styles that have been set on input[type='color'], we discover the dimensions have been set as flow-relative ones (inline-size and block-size).

Screenshot of the Firefox user agent styles showing flow relative dimensions being set on input[type='color'].
How <input type='color'> dimensions have been set in Firefox.

The final property we check for the normal state of the actual input is background. Here, Edge is the only browser to have a background-image (set to a top to bottom gradient), while Chrome and Firefox both have a background-color set to ButtonFace (another deprecated CSS2 system value). The strange thing is this should be rgb(240, 240, 240) (according to this resource), but its computed value in Chrome is rgb(221, 221, 221).

Comparative screenshots of DevTools in the three browsers showing the background values for the actual input.
The background values for <input type='color'> compared in Chrome, Firefox and Edge (from top-to-bottom).

What’s even stranger is that, if we actually look at our input in Chrome, it sure does look like it has a gradient background! If we screenshot it and then use a picker, we get that it has a top to bottom gradient from #f8f8f8 to #ddd.

Screenshot of the input in Chrome. A very light, almost white, grey to another light, but still darker grey gradient from top to bottom can be seen as the background, not the solid background-color indicated by DevTools.
What the actual input looks like in Chrome. It appears to have a gradient, in spite of the info we get from DevTools telling us it doesn’t.

Also, note that changing just the background-color (or another property not related to dimensions like border-radius) in Edge also changes the background-image, background-origin, border-color or border-style.

Animated gif. Shows the background-image, background-origin, border-color, border-style before and after changing the seemingly unrelated background-color - their values don't get preserved following this change.
Edge: side-effects of changing background-color.

Other states

We can take a look at the styles applied for a bunch of other states of an element by clicking the :hov button in the Styles panel for Chrome and Firefox and the a: button in the same Styles panel for Edge. This reveals a section where we can check the desired state(s).

Screenshot collage highlighting the buttons that bring up the states panel in Chrome, Firefox and Edge.
Taking a look at other states in Chrome, Firefox, Edge (from top to bottom).

Note that, in Firefox, checking a class only visually applies the user styles on the selected element, not the browser styles. So, if we check :hover for example, we won’t see the :hover styles applied on our element. We can however see the user agent styles matching the selected state for our selected element shown in DevTools.

Also, we cannot test for all states like this and let’s start with such a state.

:disabled

In order to see how styles change in this state, we need to manually add the disabled attribute to our <input type='color'> element.

Hmm… not much changes in any browser!

In Chrome, we see the background-color is slightly different (rgb(235, 235, 228) in the :disabled state versus rgb(221, 221, 221) in the normal state).

Chrome DevTools screenshot showing the background being set to rgb(235, 235, 228) for a :disabled input.
Chrome :disabled styling.

But the difference is only clear looking at the info in DevTools. Visually, I can tell tell there’s a slight difference between an input that’s :disabled and one that’s not if they’re side-by-side, but if I didn’t know beforehand, I couldn’t tell which is which just by looking at them, and if I just saw one, I couldn’t tell whether it’s enabled or not without clicking it.

Disabled and enabled input side by side in Chrome. There is a slight difference in background-color, but it's pretty much impossible to tell which is which just by looking at them.
Disabled (left) versus enabled (right) <input type='color'> in Chrome.

In Firefox, we have the exact same values set for the :disabled state as for the normal state (well, except for the cursor, which realistically, isn’t going to produce different results save for exceptional cases anyway). What gives, Firefox?!

Comparison of styles set in Firefox for <input type='color'> in its normal state and its :disabled state. The padding and border set in the :disabled case are exactly the same as those set in the normal case.”/><figcaption>Firefox <code>:disabled</code> (top) versus normal (bottom) styling.</figcaption></figure>
<p>In Edge, both the <code>border-color</code> and the <code>background</code> gradient are different.</p>
<figure><img src=
Edge :disabled styling (by checking computed styles).

We have the following styles for the normal state:

border-color: rgb(112, 112, 112); background-image: linear-gradient(rgb(236, 236, 236), rgb(213, 213, 213));

And for the :disabled state:

border-color: rgb(186, 186, 186); background-image: linear-gradient(rgb(237, 237, 237), rgb(229, 229, 229));

Clearly different if we look at the code and visually better than Chrome, though it still may not be quite enough:

Disabled and enabled input side by side in Edge. There is a slight difference in background-image and a bigger difference in border-color, but it still may be difficult to tell whether an input is enabled or not at first sight without having a reference to compare.
Disabled (left) versus enabled (right) <input type='color'> in Edge.
:focus

This is one state we can test by toggling the DevTools pseudo-classes. Well, in theory. In practice, it doesn’t really help us in all browsers.

Starting with Chrome, we can see that we have an outline in this state and the outline-color computes to rgb(77, 144, 254), which is some kind of blue.

Chrome DevTools screenshot showing an outline for an input having <code>:focus</code>.”/><figcaption>Chrome <code>:focus</code> styling.</figcaption></figure>
<p>Pretty straightforward and easy to spot.</p>
<p>Moving on to Firefox, things start to get hairy! Unlike Chrome, toggling the <code>:focus</code> pseudo-class from DevTools does nothing on the input element, though by focusing it (by tab click), the <code>border</code> becomes blue and we get a <code>dotted</code> rectangle within — but there’s no indication in DevTools regarding what is happening.</p>
<figure><img src=
What happens in Firefox when tabbing to our input to :focus it.

If we check Firefox’s forms.css, it provides an explanation for the dotted rectangle. This is the dotted border of a pseudo-element, ::-moz-focus-inner (a pseudo-element which, for some reason, isn’t shown in DevTools inside our input as ::-moz-color-swatch is). This border is initially transparent and then becomes visible when the input is focused — the pseudo-class used here (:-moz-focusring) is pretty much an old Firefox version of the new standard (:focus-visible), which is currently only supported by Chrome behind the Experimental Web Platform features flag.

Firefox DevTools screenshot where the inner dotted rectangle on :focus comes from: it is set as a transparent border on the ::-moz-focus-inner pseudo-element and it becomes visible when the input should have a noticeable :focus indicator.
Firefox: where the inner dotted rectangle on :focus comes from.

What about the blue border? Well, it appears this one isn’t set by a stylesheet, but at an OS level instead. The good news is we can override all these styles should we choose to do so.

In Edge, we’re faced with a similar situation. Nothing happens when toggling the :focus pseudo-class from DevTools, but if we actually tab to our input to focus it, we can see an inner dotted rectangle.

Animated gif. Shows how, on :focus, our input gets an inner dotted rectangle.
What happens in Edge when tabbing to our input to :focus it.

Even though I have no way of knowing for sure, I suspect that, just like in Firefox, this inner rectangle is due to a pseudo-element that becomes visible on :focus.

:hover

In Chrome, toggling this pseudo-class doesn’t reveal any :hover-specific styles in DevTools. Furthermore, actually hovering the input doesn’t appear to change anything visually. So it looks like Chrome really doesn’t have any :hover-specific styles?

In Firefox, toggling the :hover pseudo-class from DevTools reveals a new rule in the styles panel:

Screenshot of Firefox DevTools showing the rule set that shows up for the :hover state.
Firefox :hover styling as seen in DevTools.

When actually hovering the input, we see the background turns light blue and the border blue, so the first thought would be that light blue is the -moz-buttonhoverface value and that the blue border is again set at an OS level, just like in the :focus case.

Animated gif. Shows that, on actually hovering our <input type='color'>, it gets a light blue background and a blue border.”/><figcaption>What actually happens in Firefox on <code>:hover</code>.</figcaption></figure>
<p>However, if we look at the computed styles, we see the same <code>background</code> we have in the normal state, so that blue <code>background</code> is probably really set at an OS level as well, in spite of having that rule in the <code>forms.css</code> stylesheet.</p>
<figure><img src=
Firefox: computed background-color of an <input type='color'> on :hover.

In Edge, toggling the :hover pseudo-class from DevTools gives our input a light blue (rgb(166, 244, 255)) background and a blue (rgb(38, 160, 218)) border, whose exact values we can find in the Computed tab:

Screenshot of Edge DevTools showing the computed value for background-color and border-color in the :hover state.
Edge: computed background-color and border-color of an <input type='color'> on :hover.
:active

Checking the :active state in the Chrome DevTools does nothing visually and shows no specific rules in the Styles panel. However, if we actually click our input, we see that the background gradient that doesn’t even show up in DevTools in the normal state gets reversed.

Screenshot of the input in :active state in Chrome. A very light, almost white, grey to another light, but still darker grey gradient from bottom to top can be seen as the background, not the solid background-color indicated by DevTools.
What the actual input looks like in Chrome in the :active state. It appears to have a gradient (reversed from the normal state), in spite of the info we get from DevTools telling us it doesn’t.

In Firefox DevTools, toggling the :active state on does nothing, but if we also toggle the :hover state on, then we get a rule set that changes the inline padding (the block padding is set to the same value of 0 it has in all other states), the border-style and sets the background-color back to our old friend ButtonFace.

Screenshot of Firefox DevTools showing the rule set that shows up for the :active state.
Firefox :active styling as seen in DevTools.

In practice, however, the only thing that matches the info we get from DevTools is the inline shift given by the change in logical padding. The background becomes a lighter blue than the :hover state and the border is blue. Both of these changes are probably happening at an OS level as well.

Animated gif. Shows that, on actually clicking our <input type='color'>, it gets a light blue background and a blue border in addition to sliding 1 pixel in the inline direction as a result of changing the inline padding.”/><figcaption>What actually happens in Firefox in an <code>:active</code> state.</figcaption></figure>
<p>In Edge, activating the <code>:active</code> class from DevTools gives us the exact same styles we have for the <code>:hover</code> state. However, if we have both the <code>:hover</code> and the <code>:active</code> states on, things change a bit. We still have a light blue <code>background</code> and a blue <code>border</code>, but both are darker now (<code>rgb(52, 180, 227)</code> for the <code>background-color</code> and <code>rgb(0, 137, 180)</code> for the <code>border-color</code>):</p>
<figure><img src=
The computed background-color and border-color of an <input type='color'> on :active viewed in Edge.

This is the takeaway: if we want a consistent cross-browser results for <input type='color'>, we should define our own clearly distinguishable styles for all these states ourselves because, fortunately, almost all the browser defaults — except for the inner rectangle we get in Edge on :focus — can be overridden.

The swatch wrapper

This is a component we only see in Chrome, so if we want a cross-browser result, we should probably ensure it doesn’t affect the swatch inside — this means ensuring it has no margin, border, padding or background and that its dimensions equal those of the actual input’s content-box.

In order to know whether we need to mess with these properties (and maybe others as a result) or not, let’s see what the browser defaults are for them.

Fortunately, we have no margin or border, so we don’t need to worry about these.

Chrome DevTools screenshot showing the margin and border values for the swatch wrapper.
The margin and border values for the swatch wrapper in Chrome.

We do however have a non-zero padding (of 4px 2px), so this is something we’ll need to zero out if we want to achieve a consistent cross-browser result.

Chrome DevTools screenshot showing the padding values for the swatch wrapper.
The padding values for the swatch wrapper in Chrome.

The dimensions are both conveniently set to 100%, which means we won’t need to mess with them.

Chrome DevTools screenshot showing the size values for the swatch wrapper.
The size values for the swatch wrapper in Chrome.

Something we need to note here is that we have box-sizing set to border-box, so the padding gets subtracted from the dimensions set on this wrapper.

Chrome DevTools screenshot showing the <code>box-sizing</code> value for the swatch wrapper.”/><figcaption>The <code>box-sizing</code> value for the swatch wrapper in Chrome.</figcaption></figure>
<p>This means that while the <code>padding-box</code>, <code>border-box</code> and <code>margin-box</code> of our wrapper (all equal because we have no <code>margin</code> or <code>border</code>) are identical to the <code>content-box</code> of the actual <code><input type='color'></code> (which is <code>44px</code>x<code>23px</code> in Chrome), getting the wrapper’s <code>content-box</code> involves subtracting the <code>padding</code> from these dimensions. It results that this box is <code>40px</code>x<code>15px</code>.</p>
<figure><img src=
The box model for the swatch wrapper in Chrome.

The background is set to transparent, so that’s another property we don’t need to worry about resetting.

Chrome DevTools screenshot showing the background values for the swatch wrapper.
The background values for the swatch wrapper in Chrome.

There’s one more property set on this element that caught my attention: display. It has a value of flex, which means its children are flex items.

Chrome DevTools screenshot showing the display value for the swatch wrapper.
The display value for the swatch wrapper in Chrome.

The swatch

This is a component we can style in Chrome and Firefox. Sadly, Edge doesn’t expose it to allow us to style it, so we cannot change properties we might want to, such as border, border-radius or box-shadow.

The box-sizing property is one we need to set explicitly if we plan on giving the swatch a border or a padding because its value is content-box in Chrome, but border-box in Firefox.

Comparative screenshots of DevTools in the two browsers showing the computed values of box-sizing for the swatch component.
The box-sizing values for the swatch viewed in Chrome (top) and Firefox (bottom).

Fortunately, the font-size is inherited from the input itself so it’s the same.

Comparative screenshots of DevTools in the two browsers showing the computed values of font-size for the swatch component.
The font-size values for the swatch viewed in Chrome (top) and Firefox (bottom).

The margin computes to 0 in both Chrome and Firefox.

Comparative screenshots of DevTools in the two browsers showing the computed values of margin for the swatch component.
The margin values for the swatch viewed in Chrome (top) and Firefox (bottom).

This is because most margins haven’t been set, so they end up being 0 which is the default for <div> elements. However, Firefox is setting the inline margins to auto and we’ll be getting to why that computes to 0 in just a little moment.

Screenshot of Firefox DevTools.
The inline margin for the swatch being set to auto in Firefox.

The border is solid 1px in both browsers. The only thing that differs is the border-color, which is rgb(119, 119, 119) in Chrome and grey (or rgb(128, 128, 128), so slightly lighter) in Firefox.

Comparative screenshots of DevTools in the two browsers showing the computed values of border for the swatch component.
The border values for the swatch viewed in Chrome (top) and Firefox (bottom).

Note that the computed border-width in Firefox (at least on Windows) depends on the OS zoom level, just as it is in the case of the actual input.

The padding is luckily 0 in both Chrome and Firefox.

Comparative screenshots of DevTools in the two browsers showing the computed values of padding for the swatch component.
The padding values for the swatch viewed in Chrome (top) and Firefox (bottom).

The dimensions end up being exactly what we’d expect to find, assuming the swatch covers its parent’s entire content-box.

Comparative screenshots of DevTools in the two browsers showing the box model for the swatch component.
The box model for the swatch viewed in Chrome (top) and Firefox (bottom).

In Chrome, the swatch parent is the <div> wrapper we saw earlier, whose content-box is 4pxx15px. This is equal to the margin-box and the border-box of the swatch (which coincide as we have no margin). Since the padding is 0, the content-box and the padding-box for the swatch are identical and, subtracting the 1px border, we get dimensions that are 38pxx13px.

In Firefox, the swatch parent is the actual input, whose content-box is 44pxx19px one. This is equal to the margin-box and the border-box of the swatch (which coincide as we have no margin). Since the padding is 0, the content-box and the padding-box for the swatch are identical and, subtracting the 1px border, we get that their dimensions are 42pxx17px.

In Firefox, we see that the swatch is made to cover its parent’s content-box by having both its dimensions set to 100%.

Comparative screenshots of DevTools in the two browsers showing the size values for the swatch component.
The size values for the swatch viewed in Chrome (top) and Firefox (bottom).

This is the reason why the auto value for the inline margin computes to 0.

But what about Chrome? We cannot see any actual dimensions being set. Well, this result is due to the flex layout and the fact that the swatch is a flex item that’s made to stretch such that it covers its parent’s content-box.

Chrome DevTools screenshot showing the flex value for the swatch wrapper.
The flex value for the swatch wrapper in Chrome.

Final thoughts

Phew, we covered a lot of ground here! While it may seem exhaustive to dig this deep into one specific element, this is the sort of exercise that illustrates how difficult cross-browser support can be. We have our own styles, user agent styles and operating system styles to traverse and some of those are always going to be what they are. But, as we discussed at the very top, this winds up being an accessibility issue at the end of the day, and something to really consider when it comes to implementing a practical, functional application of a color input.

Remember, a lot of this is ripe territory to reach out to browser vendors and let them know how they can update their implementations based on your reported use cases. Here are the three tickets I mentioned earlier where you can either chime in or reference to create a new ticket:

The post Color Inputs: A Deep Dive into Cross-Browser Differences appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

A Quick Look at the First Public Working Draft for Color Adjust Module 1

We’ve been talking a lot about Dark Mode around here ever since Apple released it as a system setting in MacOS 10.14 and subsequently as part of Safari. It’s interesting because of both what it opens up as as far as design opportunities as well as tailoring user experience based on actual user preferences.

This week, we got an Editor’s Draft for the Color Adjust Module Level 1 specification and the First Public Working Draft of it. All of this is a work-in-progress, but the progression of it has been interesting to track. The spec introduces three new CSS properties that help inform how much control the user agent should have when determining the visual appearance of a rendered page based on user preferences.

color-scheme is the first property defined in the spec and perhaps the centerpiece of it. It accepts light and dark values which — as you may have guessed — correspond to Light Mode and Dark Mode preferences for operating systems that support them. And, for what it’s worth, we could be dealing with labels other than “Light” and “Dark” (e.g. “Day” and “Night”) but what we’re dealing with boils down to a light color scheme versus a dark one.

Source: developer.apple.com

This single property carries some important implications. For one, the idea is that it allows us to set styles based on a user’s system preferences which gives us fine-grained control over that experience.

Another possible implication is that declaring the property at all enables the user agent to take some responsibility for determining an element’s colors, where declaring light or dark informs the user agent that an element is “aware” of color schemes and should be styled according to a preference setting matching the value. On the other hand, we can give the browser full control to determine what color scheme to use based on the user’s system preferences by using the auto value. That tells the browser that an element is “unaware” of color schemes and that the browser can determine how to proceed using the user preferences and a systems’s default styling as a guide.

It’s worth noting at this point that we may also have a prefers-color-scheme media feature (currently in the Editor’s Draft for the Media Queries Level 5 specification) that also serves to let us detect a user’s preference and help gives us greater control of the user experience based on system preferences. Robin has a nice overview of it. The Color Adjust Module Level 1 Working Draft also makes mention of possibly using a color scheme value in a <meta> element to indicate color scheme support.

There’s more to the property, of course, including an only keyword, chaining values to indicate an order of preference, and even an open-ended custom ident keyword. So definitely dig in there because there’s a lot to take in.

Pretty interesting, right? Hopefully you’re starting to see how this draft could open up new possibilities and even impacts how we make design decisions. And that’s only the start because there are two more properties!

  • forced-color-adjust: This is used when we want to support color schemes but override the user agent’s default stylesheet with our own CSS. This includes a note about possibly merging this into color-adjust.
  • color-adjust: Unlike forcing CSS overrides onto the user agent, this property provides a hint to browsers that they can change color values based on the both the user’s preferences and other factors, such as screen quality, bandwidth, or whatever is “deem[ed] necessary and prudent for the output device.” Eric Bailey wrote up the possibilities this property could open up as far as use cases, enhanced accessibility, and general implementations.

The current draft is sure to expand but, hey, this is where we get to be aware of the awesome work that W3C authors are doing, gain context for the challenges they face, and even contribute to the work. (See Rachel Andrew’s advice on making contributions.)

The post A Quick Look at the First Public Working Draft for Color Adjust Module 1 appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , ,
[Top]

Color contrast accessibility tools

Accessibility is all the rage these days, specifically when it comes to color contrast. I’ve stumbled upon a couple of tools this week that I think are pretty nifty for helping make sure that all of the text on our websites is legible regardless of what background color they might have.

First up is the Accessible Color Generator which happens to be a wonderful tool for picking alternative colors. Let’s say you’re working on a brand with color X. You can generate a host of other complimentary colors like this:

Next up is Contrast, a rather great MacOS app that sits in the menu bar at all times and helps identify accessible color pairings based on WCAG Guidelines. This one is particularly useful if you happen to be a designer:

This reminds me of a wonderful post about how the Lyft design team re-approached the way they use color in their app. Kevyn Arnott explains:

Color, at least on the surface, appears almost naively simple, yet as it scales across larger products it becomes unbelievably complex. You have thousands of people building products all at once, and those products are all heavily reliant on color. This puts a lot of pressure on the color system to ensure that all the products are being created consistently, but very hard to implement since it’s all too easy to apply colors on a one-off basis.

The team then went ahead and built ColorBox.io which lets you systematically build out a ton of colors for your design systems work. It’s pretty nifty!

Plus the folks over at GOV.UK made their own color accessibility tool called Contrast Checker which (as you have guessed by the name) helps check the contrast between the background of an element and the page itself:

And, of course, there’s the trusty WebAIM contrast checker, which is a go-to for many developers out there.

So far, we’ve looked at tools that check contrast. But there is a class of tooling that can automate accessible contrasts during development. Josh Bader wrote up an approach that enforces high contrast by pairing CSS custom properties with the calc() function. Facundo Corradini did something similar that switches font color based on the background color behind it.

Oh! And we may have something to look forward to with the color-adjust property. It is proposed in the CSS Color Module Level 4 specification and could give browsers more control to adjust color values that are declared in the stylesheet. It’s not really geared towards color contrast, but there’s something interesting about handing off the responsibility of rendering color values to the browser based on certain conditions.

The post Color contrast accessibility tools appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Change Color of SVG on Hover

There are a lot of different ways to use SVG. Depending on which way, the tactic for recoloring that SVG in different states or conditions — :hover, :active, :focus, class name change, etc. — is different.

Let’s look at the ways.

Inline SVG

Inline SVG is my favorite way to use SVG anyway, in part because of how easy it is to access and style the SVG.

See the Pen
bJXNyy
by Chris Coyier (@chriscoyier)
on CodePen.

If you’re used to working with icon fonts, one thing you might enjoy about them is how easy it is to change the color. You’re largely limited to a single color with icon fonts in a way that SVG isn’t, but still, it is appealingly easy to change that single color with color. Using inline SVG allows you to set the fill, which cascades to all the elements within the SVG, or you can fill each element separately if needed.

SVG Symbol / Use

There is such thing as an SVG sprite, which is a group of SVGs turned into <symbol> elements such that any given icon can be referenced easily with a <use> element.

See the Pen
Use SVG Hovers
by Chris Coyier (@chriscoyier)
on CodePen.

You can still set the fill color from outside CSS rather easily this way, but there are caveats.

  • The internal SVG elements (like the <path>) can have no fill themselves. This allows the fill set from the parent SVG to cascade into the Shadow DOM created by <use>. As soon as you have something like <path fill="blue" ... /> in the <symbol>, you’ve lost outside CSS control.
  • Likewise, the fill of individual elements cannot be controlled within the SVG like you could with inline SVG. This means you’re pretty firmly in single-color territory. That covers most use cases anyway, but still, a limitation nonetheless.

SVG background images

SVG can be set as a background image just like PNG, JPG, or whatever other graphics format. At this point, you’ve sort of given up on being able to change the fill. One possibility, which I’d argue isn’t a particularly good one, is to have two versions of every icon, in the respective colors, and swap between them:

See the Pen
Background SVG Hovers
by Chris Coyier (@chriscoyier)
on CodePen.

I don’t blame you if you’d rather not swap sources, so another possibility is to get gnarly with filters.

See the Pen
Background SVG Hovers with Filters
by Chris Coyier (@chriscoyier)
on CodePen.

Trying to finagle the right filters to get the color right is tricky stuff. Fortunately, Barrett Sonntag made a tool to calculate the filters for you! Turning black to red ends up a whacky combination like this: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%) contrast(97%);.

SVG also has object, which is kinda neat in that it had a built-in fallback back in the day — although browser support is so good these days, I honestly have never used it. But if you’re using it, you would probably have to use this filter technique to swap color on hover.

See the Pen
Background SVG Object Hovers
by Chris Coyier (@chriscoyier)
on CodePen.

Use a mask instead of a background image

This way, the SVG is still in charge of essentially drawing the shape, but the color comes from the background-color (or image! or gradient!) behind it rather than the SVG itself.

See the Pen
Background SVG Hovers with Mask
by Chris Coyier (@chriscoyier)
on CodePen.

SVG background images as data URLs

This doesn’t change that much from above, but it does open up one interesting possibility: Using a variable for the internal fills. Here that is with Sass keeping the URLs as variables:

See the Pen
Background SVG Hovers with Data URL variables
by Chris Coyier (@chriscoyier)
on CodePen.

You can also do this with native CSS custom properties!

The post Change Color of SVG on Hover appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Re: Pleasing Color Palettes

There are so many tools out there to help you pick colors. I totally get it! It’s hard! When colors are done well, it’s like magic. It adds a level of polish to a design that can really set it apart.

Let’s look at some, then talk about this idea some more.

Here’s one I just saw called Color Koala:

It spits out five colors at ya and you’re off to the races.

Hue will give you some too.

There’s a billion more, and they vary in approach and features, of course. Here’s a handful:

Then there are tools that focus on gradients, like UI Gradients, Web Gradients, and Shapy.

Oh! And a site that helps with text color while keeping accessibility in mind.

There are even native apps like Sip, ColorSnapper, and Frank DeLoupe that help you select colors and sometimes keep your palettes right within them.

Colors can be programatically generated.

There is no native JavaScript API for it, but it’s still basically a one-liner:

See the Pen Generate New Random Hex Color with JavaScript by Chris Coyier (@chriscoyier) on CodePen.

Pleasing colors can be as well.

Generating random colors won’t guarantee pleasing palettes, especially if a bunch of random colors are paired together. PleaseJS can help build color schemes that work together. You provide it a base color and other options (like what type of color scheme) and it spits out colors for you.

See the Pen Generate Pleasing Colors by Chris Coyier (@chriscoyier) on CodePen.

Similarly, randomColor.js

gen­er­ates at­trac­tive col­ors by de­fault. More specif­i­cally, ran­dom­Color pro­duces bright col­ors with a rea­son­ably high sat­u­ra­tion. This makes ran­dom­Color par­tic­u­larly use­ful for data vi­su­al­iza­tions and gen­er­a­tive art.

It doesn’t claim to make multiple colors part of a cohesive theme aside from passing in a base hue or luminosity.

See the Pen Generate Pleasing Colors by Chris Coyier (@chriscoyier) on CodePen.

But the thing about just being handed colors is…

…they don’t exactly tell you how to use them. Steve Schoger makes a point of this, rather hilariously in a blog post. This is a perfectly lovely color palette:

But if you just pick those colors and plop them onto a design, you could end up with something like this:

You might like that, but you’d be in the minority. It’s not a refined design that gets out of the way and would be nice to use every day. Color usage is a bit more complicated than plopping five nice colors into a design. It’s variations on those and using them in tasteful ways, like this:

Picking up Steve Schoger and Adam Wathan’s book surely has some advice for you there!

The post Re: Pleasing Color Palettes appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Converting Color Spaces in JavaScript

A challenge I faced in building an image “emojifier” was that I needed to change the color spaces of values obtained using getImageData() from RGB to HSL. I used arrays of emojis arranged by brightness and saturation, and they were HSL-based for the best matches of average pixel colors with the emojis.

In this article, we’ll study functions that will be useful for converting both opaque and alpha-enabled color values. Modern browsers currently support the color spaces RGB(A), hex, and HSL(A). The functions and notations for these are rgb(), rgba(), #rgb/#rrggbb, #rgba/#rrggbbaa, hsl(), and hsla(). Browsers have always supported built-in names like aliceblue as well.

Balls with color values being inserted into a machine and coming out as HSL

Along the way, we’ll encounter use of some color syntaxes provided by a new Level 4 of the CSS Colors Module. For example, we now have hex with alpha as we mentioned (#rgba/#rrggbbaa) and RGB and HSL syntaxes no longer require commas (values like rgb(255 0 0) and hsl(240 100% 50%) became legal!).

Browser support for CSS Colors Level 4 isn’t universal as of this writing, so don’t expect new color syntaxes to work in Microsoft browsers or Safari if trying them in CSS.

RGB to Hex

Converting RGB to hex is merely a change of radices. We convert the red, green, and blue values from decimal to hexadecimal using toString(16). After prepending 0s to single digits and under, we can concatenate them and # to a single return statement.

function RGBToHex(r,g,b) {   r = r.toString(16);   g = g.toString(16);   b = b.toString(16);    if (r.length == 1)     r = "0" + r;   if (g.length == 1)     g = "0" + g;   if (b.length == 1)     b = "0" + b;    return "#" + r + g + b; }

RGB in String

Alternatively, we can use a single string argument with the red, green and blue separated by commas or spaces (e.g. "rgb(255,25,2)", "rgb(255 25 2)"). Substring to eliminate rgb(, split what’s left by the ), then split that result’s first item by whichever the separator (sep) is. r, g, and b shall become local variables now. Then we use + before the split strings to convert them back to numbers before obtaining the hex values.

function RGBToHex(rgb) {   // Choose correct separator   let sep = rgb.indexOf(",") > -1 ? "," : " ";   // Turn "rgb(r,g,b)" into [r,g,b]   rgb = rgb.substr(4).split(")")[0].split(sep);    let r = (+rgb[0]).toString(16),       g = (+rgb[1]).toString(16),       b = (+rgb[2]).toString(16);    if (r.length == 1)     r = "0" + r;   if (g.length == 1)     g = "0" + g;   if (b.length == 1)     b = "0" + b;    return "#" + r + g + b; }

In addition, we can allow strings with channel values as percentages by adding the loop after redefining rgb. It’ll strip the %s and turn what’s left into values out of 255.

function RGBToHex(rgb) {   let sep = rgb.indexOf(",") > -1 ? "," : " ";   rgb = rgb.substr(4).split(")")[0].split(sep);    // Convert %s to 0–255   for (let R in rgb) {     let r = rgb[R];     if (r.indexOf("%") > -1)       rgb[R] = Math.round(r.substr(0,r.length - 1) / 100 * 255);       /* Example:       75% -> 191       75/100 = 0.75, * 255 = 191.25 -> 191       */   }    ... }

Now we can supply values like either of these:

  • rgb(255,25,2)
  • rgb(255 25 2)
  • rgb(50%,30%,10%)
  • rgb(50% 30% 10%)

RGBA to Hex (#rrggbbaa)

Converting RGBA to hex with the #rgba or #rrggbbaa notation follows virtually the same process as the opaque counterpart. Since the alpha (a) is normally a value between 0 and 1, we need to multiply it by 255, round the result, then convert it to hexadecimal.

function RGBAToHexA(r,g,b,a) {   r = r.toString(16);   g = g.toString(16);   b = b.toString(16);   a = Math.round(a * 255).toString(16);    if (r.length == 1)     r = "0" + r;   if (g.length == 1)     g = "0" + g;   if (b.length == 1)     b = "0" + b;   if (a.length == 1)     a = "0" + a;    return "#" + r + g + b + a; }

To do this with one string (including with percentages), we can follow what we did earlier. Also note the extra step of splicing out a slash. Since CSS Colors Level 4 supports the syntax of rgba(r g b / a), this is where we allow it. Alpha values can now be percentages! This removes the 0-1-only shackles we used to have. Therefore, the for loop cycling through rgba shall include a part to wipe the % from the alpha without multiplying by 255 (when R is 3 for alpha). Soon we can use values like rgba(255 128 0 / 0.8) and rgba(100% 21% 100% / 30%)!

function RGBAToHexA(rgba) {   let sep = rgba.indexOf(",") > -1 ? "," : " ";   rgba = rgba.substr(5).split(")")[0].split(sep);                    // Strip the slash if using space-separated syntax   if (rgba.indexOf("/") > -1)     rgba.splice(3,1);    for (let R in rgba) {     let r = rgba[R];     if (r.indexOf("%") > -1) {       let p = r.substr(0,r.length - 1) / 100;        if (R < 3) {         rgba[R] = Math.round(p * 255);       } else {         rgba[R] = p;       }     }   } }

Then, where the channels are converted to hex, we adjust a to use an item of rgba[].

function RGBAToHexA(rgba) {   ...        let r = (+rgba[0]).toString(16),       g = (+rgba[1]).toString(16),       b = (+rgba[2]).toString(16),       a = Math.round(+rgba[3] * 255).toString(16);    if (r.length == 1)     r = "0" + r;   if (g.length == 1)     g = "0" + g;   if (b.length == 1)     b = "0" + b;   if (a.length == 1)     a = "0" + a;    return "#" + r + g + b + a; }

Now the function supports the following:

  • rgba(255,25,2,0.5)
  • rgba(255 25 2 / 0.5)
  • rgba(50%,30%,10%,0.5)
  • rgba(50%,30%,10%,50%)
  • rgba(50% 30% 10% / 0.5)
  • rgba(50% 30% 10% / 50%)

Hex to RGB

We know that the length of hex values must either be 3 or 6 (plus #). In either case, we begin each red (r), green (g), and blue (b) value with "0x" to convert them to hex. If we provide a 3-digit value, we concatenate the same value twice for each channel. If it’s a 6-digit value, we concatenate the first two for red, next two for green, and last two for blue. To get the values for the final rgb() string, we prepend the variables with + to convert them from strings back to numbers, which will yield the decimals we need.

function hexToRGB(h) {   let r = 0, g = 0, b = 0;    // 3 digits   if (h.length == 4) {     r = "0x" + h[1] + h[1];     g = "0x" + h[2] + h[2];     b = "0x" + h[3] + h[3];    // 6 digits   } else if (h.length == 7) {     r = "0x" + h[1] + h[2];     g = "0x" + h[3] + h[4];     b = "0x" + h[5] + h[6];   }      return "rgb("+ +r + "," + +g + "," + +b + ")"; }

Output RGB with %s

If we want to return rgb() using percentages, then we can modify the function to utilize an optional isPct parameter like so:

function hexToRGB(h,isPct) {   let r = 0, g = 0, b = 0;   isPct = isPct === true;    if (h.length == 4) {     r = "0x" + h[1] + h[1];     g = "0x" + h[2] + h[2];     b = "0x" + h[3] + h[3];        } else if (h.length == 7) {     r = "0x" + h[1] + h[2];     g = "0x" + h[3] + h[4];     b = "0x" + h[5] + h[6];   }        if (isPct) {     r = +(r / 255 * 100).toFixed(1);     g = +(g / 255 * 100).toFixed(1);     b = +(b / 255 * 100).toFixed(1);   }      return "rgb(" + (isPct ? r + "%," + g + "%," + b + "%" : +r + "," + +g + "," + +b) + ")"; }

Under the last if statement, using +s will convert r, g, and b to numbers. Each toFixed(1) along with them will round the result to the nearest tenth. Additionally, we won’t have whole numbers with .0 or the decades old bug that produces numbers like 0.30000000000000004. Therefore, in the return, we omitted the +s right before the first r, g, and b to prevent NaNs caused by the %s. Now we can use hexToRGB("#ff0",true) to get rgb(100%,100%,0%)!

Hex (#rrggbbaa) to RGBA

The procedure for hex values with alpha should again be similar with the last. We simply detect a 4- or 8-digit value (plus #) then convert the alpha and divide it by 255. To get more precise output but not long decimal numbers for alpha, we can use toFixed(3).

function hexAToRGBA(h) {   let r = 0, g = 0, b = 0, a = 1;    if (h.length == 5) {     r = "0x" + h[1] + h[1];     g = "0x" + h[2] + h[2];     b = "0x" + h[3] + h[3];     a = "0x" + h[4] + h[4];    } else if (h.length == 9) {     r = "0x" + h[1] + h[2];     g = "0x" + h[3] + h[4];     b = "0x" + h[5] + h[6];     a = "0x" + h[7] + h[8];   }   a = +(a / 255).toFixed(3);    return "rgba(" + +r + "," + +g + "," + +b + "," + a + ")"; }

Output RGBA with %s

For a version that outputs percentages, we can do what we did in hexToRGB()—switch r, g, and b to 0–100% when isPct is true.

function hexAToRGBA(h,isPct) {   let r = 0, g = 0, b = 0, a = 1;   isPct = isPct === true;        // Handling of digits   ...    if (isPct) {     r = +(r / 255 * 100).toFixed(1);     g = +(g / 255 * 100).toFixed(1);     b = +(b / 255 * 100).toFixed(1);   }   a = +(a / 255).toFixed(3);    return "rgba(" + (isPct ? r + "%," + g + "%," + b + "%," + a : +r + "," + +g + "," + +b + "," + a) + ")"; }

Here’s a quick fix if the alpha ought to be a percentage, too: move the statement where a is redefined above the last if statement. Then in that statement, modify a to be like r, g, and b. When isPct is true, a must also gain the %.

function hexAToRGBA(h,isPct) {   ...        a = +(a / 255).toFixed(3);   if (isPct) {     r = +(r / 255 * 100).toFixed(1);     g = +(g / 255 * 100).toFixed(1);     b = +(b / 255 * 100).toFixed(1);     a = +(a * 100).toFixed(1);   }    return "rgba(" + (isPct ? r + "%," + g + "%," + b + "%," + a + "%" : +r + "," + +g + "," + +b + "," + a) + ")"; }

When we enter #7f7fff80 now, we should get rgba(127,127,255,0.502) or rgba(49.8%,49.8%,100%,50.2%).

RGB to HSL

Obtaining HSL values from RGB or hex is a bit more challenging because there’s a larger formula involved. First, we must divide the red, green, and blue by 255 to use values between 0 and 1. Then we find the minimum and maximum of those values (cmin and cmax) as well as the difference between them (delta). We need that result as part of calculating the hue and saturation. Right after the delta, let’s initialize the hue (h), saturation (s), and lightness (l).

function RGBToHSL(r,g,b) {   // Make r, g, and b fractions of 1   r /= 255;   g /= 255;   b /= 255;    // Find greatest and smallest channel values   let cmin = Math.min(r,g,b),       cmax = Math.max(r,g,b),       delta = cmax - cmin,       h = 0,       s = 0,       l = 0; }

Next, we need to calculate the hue, which is to be determined by the greatest channel value in cmax (or if all channels are the same). If there is no difference between the channels, the hue will be 0. If cmax is the red, then the formula will be ((g - b) / delta) % 6. If green, then (b - r) / delta + 2. Then, if blue, (r - g) / delta + 4. Finally, multiply the result by 60 (to get the degree value) and round it. Since hues shouldn’t be negative, we add 360 to it, if needed.

function RGBToHSL(r,g,b) {   ...   // Calculate hue   // No difference   if (delta == 0)     h = 0;   // Red is max   else if (cmax == r)     h = ((g - b) / delta) % 6;   // Green is max   else if (cmax == g)     h = (b - r) / delta + 2;   // Blue is max   else     h = (r - g) / delta + 4;    h = Math.round(h * 60);        // Make negative hues positive behind 360°   if (h < 0)       h += 360; }

All that’s left is the saturation and lightness. Let’s calculate the lightness before we do the saturation, as the saturation will depend on it. It’s the sum of the maximum and minimum channel values cut in half ((cmax + cmin) / 2). Then delta will determine what the saturation will be. If it’s 0 (no difference between cmax and cmin), then the saturation is automatically 0. Otherwise, it’ll be 1 minus the absolute value of twice the lightness minus 1 (1 - Math.abs(2 * l - 1)). Once we have these values, we must convert them to values out of 100%, so we multiply them by 100 and round to the nearest tenth. Now we can string together our hsl().

function RGBToHSL(r,g,b) {   ...   // Calculate lightness   l = (cmax + cmin) / 2;    // Calculate saturation   s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));        // Multiply l and s by 100   s = +(s * 100).toFixed(1);   l = +(l * 100).toFixed(1);    return "hsl(" + h + "," + s + "%," + l + "%)"; }

RGB in String

For one string, split the argument by comma or space, strip the %s, and localize r, g, and b like we did before.

function RGBToHSL(rgb) {   let sep = rgb.indexOf(",") > -1 ? "," : " ";   rgb = rgb.substr(4).split(")")[0].split(sep);    for (let R in rgb) {     let r = rgb[R];     if (r.indexOf("%") > -1)       rgb[R] = Math.round(r.substr(0,r.length - 1) / 100 * 255);   }    // Make r, g, and b fractions of 1   let r = rgb[0] / 255,       g = rgb[1] / 255,       b = rgb[2] / 255;    ... }

RGBA to HSLA

Compared to what we just did to convert RGB to HSL, the alpha counterpart will be basically nothing! We just reuse the code for RGB to HSL (the multi-argument version), leave a alone, and pass a to the returned HSLA. Keep in mind it should be between 0 and 1.

function RGBAToHSLA(r,g,b,a) {   // Code for RGBToHSL(r,g,b) before return   ...    return "hsla(" + h + "," + s + "%," +l + "%," + a + ")"; }

RGBA in String

For string values, we apply the splitting and stripping logic again but use the fourth item in rgba for a. Remember the new rgba(r g b / a) syntax? We’re employing the acceptance of it as we did for RGBAToHexA(). Then the rest of the code is the normal RGB-to-HSL conversion.

function RGBAToHSLA(rgba) {   let sep = rgba.indexOf(",") > -1 ? "," : " ";   rgba = rgba.substr(5).split(")")[0].split(sep);    // Strip the slash if using space-separated syntax   if (rgba.indexOf("/") > -1)     rgba.splice(3,1);    for (let R in rgba) {     let r = rgba[R];     if (r.indexOf("%") > -1) {       let p = r.substr(0,r.length - 1) / 100;        if (R < 3) {         rgba[R] = Math.round(p * 255);       } else {         rgba[R] = p;       }     }   }    // Make r, g, and b fractions of 1   let r = rgba[0] / 255,       g = rgba[1] / 255,       b = rgba[2] / 255,       a = rgba[3];    // Rest of RGB-to-HSL logic   ... }

Wish to leave the alpha as is? Remove the else statement from the for loop.

for (let R in rgba) {   let r = rgba[R];   if (r.indexOf("%") > -1) {     let p = r.substr(0,r.length - 1) / 100;      if (R < 3) {       rgba[R] = Math.round(p * 255);     }   } }

HSL to RGB

It takes slightly less logic to convert HSL back to RGB than the opposite way. Since we’ll use a range of 0–100 for the saturation and lightness, the first step is to divide them by 100 to values between 0 and 1. Next, we find chroma (c), which is color intensity, so that’s (1 - Math.abs(2 * l - 1)) * s. Then we use x for the second largest component (first being chroma), the amount to add to each channel to match the lightness (m), and initialize r, g, b.

function HSLToRGB(h,s,l) {   // Must be fractions of 1   s /= 100;   l /= 100;    let c = (1 - Math.abs(2 * l - 1)) * s,       x = c * (1 - Math.abs((h / 60) % 2 - 1)),       m = l - c/2,       r = 0,       g = 0,       b = 0; }

The hue will determine what the red, green, and blue should be depending on which 60° sector of the color wheel it lies.

Color wheel
The color wheel divided into 60° segments

Then c and x shall be assigned as shown below, leaving one channel at 0. To get the final RGB value, we add m to each channel, multiply it by 255, and round it.

function HSLToRGB(h,s,l) {   ...    if (0 <= h && h < 60) {     r = c; g = x; b = 0;   } else if (60 <= h && h < 120) {     r = x; g = c; b = 0;   } else if (120 <= h && h < 180) {     r = 0; g = c; b = x;   } else if (180 <= h && h < 240) {     r = 0; g = x; b = c;   } else if (240 <= h && h < 300) {     r = x; g = 0; b = c;   } else if (300 <= h && h < 360) {     r = c; g = 0; b = x;   }   r = Math.round((r + m) * 255);   g = Math.round((g + m) * 255);   b = Math.round((b + m) * 255);    return "rgb(" + r + "," + g + "," + b + ")"; }

HSL in String

For the single string version, we modify the first few statements basically the same way we did for RGBToHSL(r,g,b). Remove s /= 100; and l /= 100; and we’ll use the new statements to wipe the first 4 characters and the ) for our array of HSL values, then the %s from s and l before dividing them by 100.

function HSLToRGB(hsl) {   let sep = hsl.indexOf(",") > -1 ? "," : " ";   hsl = hsl.substr(4).split(")")[0].split(sep);    let h = hsl[0],       s = hsl[1].substr(0,hsl[1].length - 1) / 100,       l = hsl[2].substr(0,hsl[2].length - 1) / 100;    ... }

The next handful of statements shall handle hues provided with a unit—degrees, radians, or turns. We multiply radians by 180/π and turns by 360. If the result ends up over 360, we compound modulus divide to keep it within the scope. All of this will happen before we deal with c, x, and m.

function HSLToRGB(hsl) {   ...    // Strip label and convert to degrees (if necessary)   if (h.indexOf("deg") > -1)     h = h.substr(0,h.length - 3);   else if (h.indexOf("rad") > -1)     h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI));   else if (h.indexOf("turn") > -1)     h = Math.round(h.substr(0,h.length - 4) * 360);   // Keep hue fraction of 360 if ending up over   if (h >= 360)     h %= 360;        // Conversion to RGB begins   ... }

After implementing the steps above, now the following can be safely used:

  • hsl(180 100% 50%)
  • hsl(180deg,100%,50%)
  • hsl(180deg 100% 50%)
  • hsl(3.14rad,100%,50%)
  • hsl(3.14rad 100% 50%)
  • hsl(0.5turn,100%,50%)
  • hsl(0.5turn 100% 50%)

Whew, that’s quite the flexibility!

Output RGB with %s

Similarly, we can modify this function to return percent values just like we did in hexToRGB().

function HSLToRGB(hsl,isPct) {   let sep = hsl.indexOf(",") > -1 ? "," : " ";   hsl = hsl.substr(4).split(")")[0].split(sep);   isPct = isPct === true;    ...    if (isPct) {     r = +(r / 255 * 100).toFixed(1);     g = +(g / 255 * 100).toFixed(1);     b = +(b / 255 * 100).toFixed(1);   }    return "rgb("+ (isPct ? r + "%," + g + "%," + b + "%" : +r + "," + +g + "," + +b) + ")"; }

HSLA to RGBA

Once again, handling alphas will be a no-brainer. We can reapply the code for the original HSLToRGB(h,s,l) and add a to the return.

function HSLAToRGBA(h,s,l,a) {   // Code for HSLToRGB(h,s,l) before return   ...    return "rgba(" + r + "," + g + "," + b + "," + a + ")"; }

HSLA in String

Changing it to one argument, the way we’ll handle strings here will be not too much different than what we did earlier. A new HSLA syntax from Colors Level 4 uses (value value value / value) just like RGBA, so having the code to handle it, we’ll be able to plug in something like hsla(210 100% 50% / 0.5) here.

function HSLAToRGBA(hsla) {   let sep = hsla.indexOf(",") > -1 ? "," : " ";   hsla = hsla.substr(5).split(")")[0].split(sep);    if (hsla.indexOf("/") > -1)     hsla.splice(3,1);    let h = hsla[0],       s = hsla[1].substr(0,hsla[1].length - 1) / 100,       l = hsla[2].substr(0,hsla[2].length - 1) / 100,       a = hsla[3];            if (h.indexOf("deg") > -1)     h = h.substr(0,h.length - 3);   else if (h.indexOf("rad") > -1)     h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI));   else if (h.indexOf("turn") > -1)     h = Math.round(h.substr(0,h.length - 4) * 360);   if (h >= 360)     h %= 360;    ... }

Furthermore, these other combinations have become possible:

  • hsla(180,100%,50%,50%)
  • hsla(180 100% 50% / 50%)
  • hsla(180deg,100%,50%,0.5)
  • hsla(3.14rad,100%,50%,0.5)
  • hsla(0.5turn 100% 50% / 50%)

RGBA with %s

Then we can replicate the same logic for outputting percentages, including alpha. If the alpha should be a percentage (searched in pctFound), here’s how we can handle it:

  1. If r, g, and b are to be converted to percentages, then a should be multiplied by 100, if not already a percentage. Otherwise, drop the %, and it’ll be added back in the return.
  2. If r, g, and b should be left alone, then remove the % from a and divide a by 100.
function HSLAToRGBA(hsla,isPct) {   // Code up to slash stripping   ...        isPct = isPct === true;        // h, s, l, a defined to rounding of r, g, b   ...        let pctFound = a.indexOf("%") > -1;        if (isPct) {     r = +(r / 255 * 100).toFixed(1);     g = +(g / 255 * 100).toFixed(1);     b = +(b / 255 * 100).toFixed(1);     if (!pctFound) {       a *= 100;     } else {       a = a.substr(0,a.length - 1);     }            } else if (pctFound) {     a = a.substr(0,a.length - 1) / 100;   }    return "rgba("+ (isPct ? r + "%," + g + "%," + b + "%," + a + "%" : +r + ","+ +g + "," + +b + "," + +a) + ")"; }

Hex to HSL

You might think this one and the next are crazier processes than the others, but they merely come in two parts with recycled logic. First, we convert the hex to RGB. That gives us the base 10s we need to convert to HSL.

function hexToHSL(H) {   // Convert hex to RGB first   let r = 0, g = 0, b = 0;   if (H.length == 4) {     r = "0x" + H[1] + H[1];     g = "0x" + H[2] + H[2];     b = "0x" + H[3] + H[3];   } else if (H.length == 7) {     r = "0x" + H[1] + H[2];     g = "0x" + H[3] + H[4];     b = "0x" + H[5] + H[6];   }   // Then to HSL   r /= 255;   g /= 255;   b /= 255;   let cmin = Math.min(r,g,b),       cmax = Math.max(r,g,b),       delta = cmax - cmin,       h = 0,       s = 0,       l = 0;    if (delta == 0)     h = 0;   else if (cmax == r)     h = ((g - b) / delta) % 6;   else if (cmax == g)     h = (b - r) / delta + 2;   else     h = (r - g) / delta + 4;    h = Math.round(h * 60);    if (h < 0)     h += 360;    l = (cmax + cmin) / 2;   s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));   s = +(s * 100).toFixed(1);   l = +(l * 100).toFixed(1);    return "hsl(" + h + "," + s + "%," + l + "%)"; }

Hex (#rrggbbaa) to HSLA

There aren’t too many lines that change in this one. We’ll repeat what we recently did to get the alpha by converting the hex, but won’t divide it by 255 right away. First, we must get the hue, saturation, and lightness as we did in the other to-HSL functions. Then, before the ending return, we divide the alpha and set the decimal places.

function hexAToHSLA(H) {   let r = 0, g = 0, b = 0, a = 1;    if (H.length == 5) {     r = "0x" + H[1] + H[1];     g = "0x" + H[2] + H[2];     b = "0x" + H[3] + H[3];     a = "0x" + H[4] + H[4];   } else if (H.length == 9) {     r = "0x" + H[1] + H[2];     g = "0x" + H[3] + H[4];     b = "0x" + H[5] + H[6];     a = "0x" + H[7] + H[8];   }    // Normal conversion to HSL   ...            a = (a / 255).toFixed(3);                    return "hsla("+ h + "," + s + "%," + l + "%," + a + ")"; }

HSL to Hex

This one starts as a conversion to RGB, but there’s an extra step to the Math.round()s of converting the RGB results to hex.

function HSLToHex(h,s,l) {   s /= 100;   l /= 100;    let c = (1 - Math.abs(2 * l - 1)) * s,       x = c * (1 - Math.abs((h / 60) % 2 - 1)),       m = l - c/2,       r = 0,       g = 0,       b = 0;    if (0 <= h && h < 60) {     r = c; g = x; b = 0;   } else if (60 <= h && h < 120) {     r = x; g = c; b = 0;   } else if (120 <= h && h < 180) {     r = 0; g = c; b = x;   } else if (180 <= h && h < 240) {     r = 0; g = x; b = c;   } else if (240 <= h && h < 300) {     r = x; g = 0; b = c;   } else if (300 <= h && h < 360) {     r = c; g = 0; b = x;   }   // Having obtained RGB, convert channels to hex   r = Math.round((r + m) * 255).toString(16);   g = Math.round((g + m) * 255).toString(16);   b = Math.round((b + m) * 255).toString(16);    // Prepend 0s, if necessary   if (r.length == 1)     r = "0" + r;   if (g.length == 1)     g = "0" + g;   if (b.length == 1)     b = "0" + b;    return "#" + r + g + b; }

HSL in String

Even the first few lines of this function will be like those in HSLToRGB() if we changed it to accept a single string. This is how we’ve been obtaining the hue, saturation, and lightness separately in the first place. Let’s not forget the step to remove the hue label and convert to degrees, too. All of this will be in place of s /= 100; and l /= 100;.

function HSLToHex(hsl) {   let sep = hsl.indexOf(",") > -1 ? "," : " ";   hsl = hsl.substr(4).split(")")[0].split(sep);    let h = hsl[0],       s = hsl[1].substr(0,hsl[1].length - 1) / 100,       l = hsl[2].substr(0,hsl[2].length - 1) / 100;            // Strip label and convert to degrees (if necessary)   if (h.indexOf("deg") > -1)     h = h.substr(0,h.length - 3);   else if (h.indexOf("rad") > -1)     h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI));   else if (h.indexOf("turn") > -1)     h = Math.round(h.substr(0,h.length - 4) * 360);   if (h >= 360)     h %= 360;    ... }

HSLA to Hex (#rrggbbaa)

Adding alpha to the mix, we convert a to hex and add a fourth if to prepend a 0, if necessary. You probably already familiar with this logic because we last used it in RGBAToHexA().

function HSLAToHexA(h,s,l,a) {   // Repeat code from HSLToHex(h,s,l) until 3 `toString(16)`s   ...    a = Math.round(a * 255).toString(16);    if (r.length == 1)     r = "0" + r;   if (g.length == 1)     g = "0" + g;   if (b.length == 1)     b = "0" + b;   if (a.length == 1)     a = "0" + a;    return "#" + r + g + b + a; }

HSLA in String

Finally, the lines of the single argument version up to a = hsla[3] are no different than those of HSLAToRGBA().

function HSLAToHexA(hsla) {   let sep = hsla.indexOf(",") > -1 ? "," : " ";   hsla = hsla.substr(5).split(")")[0].split(sep);        // Strip the slash   if (hsla.indexOf("/") > -1)     hsla.splice(3,1);        let h = hsla[0],       s = hsla[1].substr(0,hsla[1].length - 1) / 100,       l = hsla[2].substr(0,hsla[2].length - 1) / 100,       a = hsla[3];                ... }

Built-in Names

To convert a named color to RGB, hex, or HSL, you might consider turning this table of 140+ names and hex values into a massive object at the start. The truth is that we really don’t need one because here’s what we can do:

  1. Create an element
  2. Give it a text color
  3. Obtain the value of that property
  4. Remove the element
  5. Return the stored color value, which will be in RGB by default

So, our function to get RGB will only be seven statements!

function nameToRGB(name) {   // Create fake div   let fakeDiv = document.createElement("div");   fakeDiv.style.color = name;   document.body.appendChild(fakeDiv);    // Get color of div   let cs = window.getComputedStyle(fakeDiv),       pv = cs.getPropertyValue("color");    // Remove div after obtaining desired color value   document.body.removeChild(fakeDiv);    return pv; }

Let’s go even further. How about we change the output to hex instead?

function nameToHex(name) {   // Get RGB from named color in temporary div   let fakeDiv = document.createElement("div");   fakeDiv.style.color = name;   document.body.appendChild(fakeDiv);    let cs = window.getComputedStyle(fakeDiv),       pv = cs.getPropertyValue("color");    document.body.removeChild(fakeDiv);    // Code ripped from RGBToHex() (except pv is substringed)   let rgb = pv.substr(4).split(")")[0].split(","),       r = (+rgb[0]).toString(16),       g = (+rgb[1]).toString(16),       b = (+rgb[2]).toString(16);    if (r.length == 1)     r = "0" + r;   if (g.length == 1)     g = "0" + g;   if (b.length == 1)     b = "0" + b;    return "#" + r + g + b; }

Or, why not HSL? 😉

function nameToHSL(name) {   let fakeDiv = document.createElement("div");   fakeDiv.style.color = name;   document.body.appendChild(fakeDiv);    let cs = window.getComputedStyle(fakeDiv),       pv = cs.getPropertyValue("color");    document.body.removeChild(fakeDiv);    // Code ripped from RGBToHSL() (except pv is substringed)   let rgb = pv.substr(4).split(")")[0].split(","),       r = rgb[0] / 255,       g = rgb[1] / 255,       b = rgb[2] / 255,       cmin = Math.min(r,g,b),       cmax = Math.max(r,g,b),       delta = cmax - cmin,       h = 0,       s = 0,       l = 0;    if (delta == 0)     h = 0;   else if (cmax == r)     h = ((g - b) / delta) % 6;   else if (cmax == g)     h = (b - r) / delta + 2;   else     h = (r - g) / delta + 4;    h = Math.round(h * 60);    if (h < 0)     h += 360;    l = (cmax + cmin) / 2;   s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));   s = +(s * 100).toFixed(1);   l = +(l * 100).toFixed(1);    return "hsl(" + h + "," + s + "%," + l + "%)"; }

In the long run, every conversion from a name becomes a conversion from RGB after cracking the name.

Validating Colors

In all these functions, there haven’t been any measures to prevent or correct ludicrous input (say hues over 360 or percentages over 100). If we’re only manipulating pixels on a <canvas> fetched using getImageData(), validation of color values isn’t necessary before converting because they’ll be correct no matter what. If we’re creating a color conversion tool where users supply the color, then validation would be much needed.

It’s easy to handle improper input for channels as separate arguments, like this for RGB:

// Correct red if (r > 255)   r = 255; else if (r < 0)   r = 0;

If validating a whole string, then a regular expression is needed. For instance, this is the RGBToHex() function given a validation step with an expression:

function RGBToHex(rgb) {   // Expression for rgb() syntaxes   let ex = /^rgb((((((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]),s?)){2}|((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5])s)){2})((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]))|((((([1-9]?d(.d+)?)|100|(.d+))%,s?){2}|((([1-9]?d(.d+)?)|100|(.d+))%s){2})(([1-9]?d(.d+)?)|100|(.d+))%)))$ /i;    if (ex.test(rgb)) {     // Logic to convert RGB to hex     ...    } else {     // Something to do if color is invalid   } }

To test other types of values, below is a table of expressions to cover both opaque and alpha-enabled:

Color Value RegEx
RGB /^rgb((((((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]),s?)){2}|((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5])s)){2})((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]))|((((([1-9]?d(.d+)?)|100|(.d+))%,s?){2}|((([1-9]?d(.d+)?)|100|(.d+))%s){2})(([1-9]?d(.d+)?)|100|(.d+))%)))$ /i
RGBA /^rgba((((((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]),s?)){3})|(((([1-9]?d(.d+)?)|100|(.d+))%,s?){3}))|(((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5])s){3})|(((([1-9]?d(.d+)?)|100|(.d+))%s){3}))/s)((0?.d+)|[01]|(([1-9]?d(.d+)?)|100|(.d+))%))$ /i
Hex /^#([da-f]{3}){1,2}$ /i
Hex (with Alpha) /^#([da-f]{4}){1,2}$ /i
HSL /^hsl(((((([12]?[1-9]?d)|[12]0d|(3[0-5]d))(.d+)?)|(.d+))(deg)?|(0|0?.d+)turn|(([0-6](.d+)?)|(.d+))rad)((,s?(([1-9]?d(.d+)?)|100|(.d+))%){2}|(s(([1-9]?d(.d+)?)|100|(.d+))%){2}))$ /i
HSLA /^hsla(((((([12]?[1-9]?d)|[12]0d|(3[0-5]d))(.d+)?)|(.d+))(deg)?|(0|0?.d+)turn|(([0-6](.d+)?)|(.d+))rad)(((,s?(([1-9]?d(.d+)?)|100|(.d+))%){2},s?)|((s(([1-9]?d(.d+)?)|100|(.d+))%){2}s/s))((0?.d+)|[01]|(([1-9]?d(.d+)?)|100|(.d+))%))$ /i

Looking at the expressions for RGB(A) and HSL(A), you probably have big eyes right now; these were made comprehensive enough to include most of the new syntaxes from CSS Colors Level 4. Hex, on the other hand, doesn’t need expressions as long as the others because of only digit counts. In a moment, we’ll dissect these and decipher the parts. Note that case-insensitive values (/i) pass all these.

RGB

/^rgb((((((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]),s?)){2}|((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5])s)){2})((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]))|((((([1-9]?d(.d+)?)|100|(.d+))%,s?){2}|((([1-9]?d(.d+)?)|100|(.d+))%s){2})(([1-9]?d(.d+)?)|100|(.d+))%)))$ /i

Because rgb() accepts either all integers or all percentages, both cases are covered. In the outmost group, between the ^rgb( and )$ , there are inner groups for both integers and percentages, all comma-spaces or spaces only as separators:

  1. (((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]),s?){2}|(((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5])s){2})((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]))
  2. ((((([1-9]?d(.d+)?)|100|(.d+))%,s?){2}|((([1-9]?d(.d+)?)|100|(.d+))%s){2})(([1-9]?d(.d+)?)|100|(.d+))%)

In the first half, we accept two instances of integers for red and green from 0–99 or 111-199 ((1?[1-9]?d)), 100–109 (10d), 200-249 ((2[0-4]d)), or 250–255 (25[0-5]). We couldn’t simply do d{1,3} because values like 03 or 017 and those greater than 255 shouldn’t be allowed. After that goes the comma and optional space (,s?). On the other side of the |, after the first {2} (which indicates two instances of integers), we check for the same thing with space separators if the left side is false. Then for blue, the same should be accepted, but without a separator.

In the other half, acceptable values for percentages, including floats, should either be 0–99, explicitly 100 and not a float, or floats under 1 with the 0 dropped. Therefore, the segment here is (([1-9]?d(.d+)?)|100|(.d+)), and it appears three times; twice with separator (,s?){2}, %s){2}), once without.

It is legal to use percentages without space separators (rgb(100%50%10%) for instance) in CSS, but the functions we wrote don’t support that. The same goes for rgba(100%50%10%/50%), hsl(40 100%50%), and hsla(40 100%50%/0.5). This could very well be a plus for code golfing and minification!

RGBA

/^rgba((((((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]),s?)){3})|(((([1-9]?d(.d+)?)|100|(.d+))%,s?){3}))|(((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5])s){3})|(((([1-9]?d(.d+)?)|100|(.d+))%s){3}))/s)((0?.d+)|[01]|(([1-9]?d(.d+)?)|100|(.d+))%))$ /i

The next expression is very similar to the pervious, but three instances of integers (((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]),s?){3})) or percentages ((((([1-9]?d(.d+)?)|100|(.d+))%,s?){3})), plus comma optional space are checked. Otherwise, it looks for the same thing but with space separators, plus a slash and space (/s) after the blue. Next to that is ((0?.d+)|[01]|(([1-9]?d(.d+)?)|100|(.d+))%) where we accept floats with or without the first 0 ((0?.d+)), 0 or 1 ([01]) on the dot, or 0–100% ((([1-9]?d(.d+)?)|100|(.d+))%).

Hex with Alpha

// #rgb/#rrggbb /^#([da-f]{3}){1,2}$ /i // #rgba/#rrggbbaa /^#([da-f]{4}){1,2}$ /i

For both hex—with and without alpha—instances of numbers or letters a–f ([da-f]) are accepted. Then one or two instances of this are counted for either short or longhand values supplied (#rgb or #rrggbb). As an illustration, we have this same short pattern: /^#([da-f]{n}){1,2}$ /i. Simply change n to 3 or 4.

HSL and HSLA

// HSL /^hsl(((((([12]?[1-9]?d)|[12]0d|(3[0-5]d))(.d+)?)|(.d+))(deg)?|(0|0?.d+)turn|(([0-6.d+)?)|(.d+))rad)((,s?(([1-9]?d(.d+)?)|100|(.d+))%){2}|(s(([1-9]?d(.d+)?)|100|(.d+))%){2}))$ /i // HSLA /^hsla(((((([12]?[1-9]?d)|[12]0d|(3[0-5]d))(.d+)?)|(.d+))(deg)?|(0|0?.d+)turn|(([0-6.d+)?)|(.d+))rad)(((,s?(([1-9]?d(.d+)?)|100|(.d+))%){2},s?)|((s(([1-9]?d(.d+)?)|100|(.d+))%){2}s/s))((0?.d+)|[01]|(([1-9]?d(.d+)?)|100|(.d+))%))$ /i

After the ( in both expressions for HSL and HSLA, this large chunk is for the hue:

((((([12]?[1-9]?d)|[12]0d|(3[0-5]d))(.d+)?)|(.d+))(deg)?|(0|0?.d+)turn|(([0-6.d+)?)|(.d+))rad)

([12]?[1-9]?d) covers 0–99, 110–199, and 210–299. [12]0d covers 110–109 and 200–209. Then (3[0-5]d) takes care of 300–359. The reason for this division of ranges is similar to that of integers in the rgb() syntax: ruling out zeros coming first and values greater than the maximum. Since hues can be floating point numbers, the first (.d+)? is for that.

Next to the | after the aforementioned segment of code, the second (.d+) is for floats without a leading zero.

Now let’s move up a level and decipher the next small chunk:

(deg)?|(0|0?.d+)turn|(([0-6.d+)?)|(.d+))rad

This contains the labels we can use for the hue—degrees, turns, or radians. We can include all or none of deg. Values in turn must be under 1. For radians, we can accept any float between 0–7. We do know, however, that one 360° turn is 2π, and it stops approximately at 6.28. You may think 6.3 and over shouldn’t be accepted. Because 2π is an irrational number, it would be too messy for this example to try to satisfy every decimal place provided by the JavaScript console. Besides, we have this snippet in our HSLTo_() functions as a second layer of security if hues 360° or over were to happen:

// Keep hue fraction of 360 if ending up over if (h >= 360)   h %= 360;

Now let’s move up a level and decipher the second chunk:

(,s?(([1-9]?d(.d+)?)|100|(.d+))%){2}

We’re counting two instances of comma-space-percentages for the saturation and lightness (space optional). In the group after the ,s?, we test for values 0–99 with or without decimal points (([1-9]?d(.d+)?)), exactly 100, or floats under 1 without the leading 0 ((.d+)).

The last part the HSL expression, before the ending ()$ /i), is a similar expression if spaces are the only separator:

(s(([1-9]?d(.d+)?)|100|(.d+))%){2}

s is in the beginning instead of ,s?. Then in the HSLA expression, this same chunk is inside another group with ,s? after its {2}.

((,s?(([1-9]?d(.d+)?)|100|(.d+))%){2},s?)

That counts the comma-space between the lightness and alpha. Then if we have spaces as separators, we need to check for a space-slash-space (s/s) after counting two instances of space and a percentage.

((s(([1-9]?d(.d+)?)|100|(.d+))%){2}s/s))

After that, we have this left to check the alpha value:

(((0?.d+)|[01])|(([1-9]?d(.d+)?)|100|(.d+))%)

Matches for (0?.d+) include floats under 1 with or without the leading 0, 0 or 1 for [01], and 0–100%.

Conclusion

If your current challenge is to convert one color space to another, you now have some ideas on how to approach it. Because it would be tiresome to walk through converting every color space ever invented in one post, we discussed the most practical and browser-supported ones. If you’d like to go beyond supported color spaces (say CMYK, XYZ, or CIE L*a*b*), EasyRGB) provides an amazing set of code-ready formulas.

To see all the conversions demonstrated here, I’ve set up a CodePen demo that shows inputs and outputs in a table. You can try different colors in lines 2–10 and see the complete functions in the JavaScript panel.

See the Pen Color Conversion by Jon Kantner (@jkantner) on CodePen.

The post Converting Color Spaces in JavaScript appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

What do you name color variables?

I remember the very first time I tried Sass on a project. The first thing I wanted to do was variablize my colors. From my naming-things-in-HTML skillz, I knew to avoid classes like .header-blue-left-bottom because the color and position of that element might change. It’s better for the to reflect it what it is than what it looks like.

So, I tried to make my colors semantic, in a sense — what they represent not what they literally are:

$ mainBrandColor: #F060D6; $ secondaryFocus: #4C9FEB; $ fadedHighlight: #F1F3F4;

But I found that I absolutely never remembered them and had to constantly refer to where I defined them in order to use them. Later, in a “screw it” moment, I named colors more like…

$ orange: #F060D6; $ red: #BB532E; $ blue: #4C9FEB;  $ gray-1: #eee; $ gray-2: #ccc; $ gray-3: #555;

I found that to be much more intuitive with little if any negative side effects. After all, this isn’t crossing the HTML-CSS boundary here; this is all within CSS and developer-only-facing, which puts more of a narrow scope on the problem.

In a similar fashion, I’ve tried keeping colors within a Sass map, like:

$ colors: (   light: #ccc,   dark: #333 );

But the only vague goal there was to clean up the global namespace, which probably isn’t worth the hassle of needing to map-get all the time. Namespacing like $ -c-orange is probably an easier approach if you need to do anything at all.

I’ve largely stuck with that just-use-color-names approach today in Sass. As the shift toward CSS custom properties happens, I think having a --c-orange and --c-gray-5 is similarly appropriate.

:root {   -c-orange: #F060D6;   -c-red: #BB532E;   -c-blue: #4C9FEB;    -c-gray-1: #eee;   -c-gray-2: #ccc;   -c-gray-3: #555; }

You could get a little more specific with those names with staying abstract, like Marcus Ortense says:

$ color-primary: $ color-primary-dark: $ color-primary-light: 

And variations on each base like Mike Street says:

$ primary: $ primaryLight:  $ primaryDark:  $ secondary: $ secondaryLight: $ secondaryDark:  $ neutralDarker: $ neutrayDark: $ neutral: $ neutralLight: $ neutralLighter:  $ neutralLightest: 

Silvestar Bistrović recently wrote about using abstract Greek numbering:

$ color-alpha: #12e09f; $ color-beta: #e01258; $ color-gamma: #f5f5f5; $ color-psi: #1f1f1f; $ color-omega: #fff;

I’ve used that kind of thing for media query breakpoints before, as the numbering seems to make sense there (i.e. low numbers are smaller screens, big numbers are bigger screens). I could also see that being nice for tints or shades of the same color, but then why not regular numbers?

Another approach I’ve often seen is to combine named colors with abstracted names. Geoff does that and John Carroll lists that here:

$ color-danube: #668DD1; $ color-cornflower: $ 6195ED; $ color-east-bay: $ 3A4D6E;  // theme1.scss $ color-alpha: $ color-danube; $ color-bravo: $ color-cornflower; $ color-charlie: $ color-east-bay;  // theme2.scss $ color-alpha: $ color-cornflower; $ color-bravo: $ color-danube; $ color-charlie: $ color-east-bay;

That can get as verbose as you need it to, even adding variations as you call from the palette.

$ table-row-background: lighten($ color-background, 10%);

Stuart Robson even gets a bit BEM-y with the names, including the namespace:

$ ns-color__blue—dark: rgb(25,25,112);  $ ns-brand__color—primary: $ ns-color__blue—dark;  // component.scss $ ns-component__color—text: $ ns-brand__color—primary;

Material Design uses values that are similar to font-weight! That means you’d end up with something like a base range plus alternates:

$ light-green-100: $ light-green-200: $ light-green-300: // etc $ light-green-900: $ light-green-A200: $ light-green-A400:  $ deep-purple-100: $ deep-purple-200: $ deep-purple-300: // etc $ deep-purple-A900:

How might you pick names for colors? You might get a kick out of what to call a sunny yellow versus a sunflower yellow, or you might just want some help. Here’s one project for that, and here’s another:

See the Pen Color Namer by Maneesh (@maneeshc) on CodePen.

There is even a Sublime Text plugin for converting them (to whatever syntax you want):

And since we’re on the topic of naming:

The post What do you name color variables? appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]