Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New API #9

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft

New API #9

wants to merge 13 commits into from

Conversation

Ptico
Copy link
Contributor

@Ptico Ptico commented May 27, 2021

HSLuv have a great potential in a design systems, where designer may define base colors or even a set of hue values and the system will take care of rest. To achieve this, we should provide a set of tools for native HSLuv color manipulation.

The first commit contains mocks for future API which I want to discuss (second hardest thing in a computer science). In general it looks like:

@use "hsluv/tools" as hsluv;
@import "hsluv/hsluv";

button {
  $baseColor: hsluv.color(10deg, 80%, 60%);
  $backgroundColor: hsluv.contrast-darken($baseColor, 'AA');
  $borderColor: hsluv.rotate($backgroundColor, 10deg);
  
  color: hsluv($baseColor);
  background-color: hsluv($backgroundColor);
  border: 1px solid hsluv($borderColor);
}

@Ptico
Copy link
Contributor Author

Ptico commented May 27, 2021

I still not sure if it's a good idea to have @use and @import in one file, maybe we may consider to add hsluv.to-hsluv() and hsluv.to-hpluv() functions to be able to just @use. Naming of hsluv.color() can be confusing as for me, an alternative is something like hsluv.create()

Also, color contrast functions accepts both numeric ratio and AA and AAA values which will contain a small reduction to satisfy automated color contrast checkers (some colors after conversion to RGB may produce a little bit lower contrast ratio)

@boronine @apexskier what do you think?

@boronine
Copy link
Member

boronine commented Jun 1, 2021

Hi @Ptico sorry for the delay.

I just looked at the Sass color module here:
https://sass-lang.com/documentation/modules/color

I'm not really impressed with their API, there's too many functions that are poorly defined. Some things just don't make sense, what happens when you adjust lightness to be above 100% or below 0%? Presumably it clips.. The API suggests using scale to adjust by percentages, but this seems even worse, for example if you scale lightness by 50% for two different colors you might have a huge change in one color and a tiny change in another color, so what is 50% supposed to mean intuitively? At this point it seems easier to just use a color picker.

I think we should make a smaller, lower level API that gives users an opportunity to do EXACTLY what they want, relying on the sass:math module. Once we have that we can consider adding high-level helper functions.

At minimum, it seems that we need functions to extract HSLuv values from an existing color, e.g. their official brand color:

  • get-hue(#abc123) -> deg
  • get-saturation(#abc123) -> %
  • get-lightness(#abc123) -> %
  • get-hpluv-saturation(#abc123) -> % (this only works with a subset of colors, we can probably use @error to deal with this)

(Or perhaps just implement get-hsluv and get-hpluv functions instead?)

The user can then use sass:math to manipulate these values and plug them back into the hsluv contructor to get new colors.

For contrast, the minimum implementation would be something like this:

  • get-contrast-ratio($lightness1 %, $lightness2 %)
  • get-darker($lightness %, $ratio) -> % (throws error when out of bounds)
  • get-lighter($lightness %, $ratio) -> % (throws error when out of bounds)

Along with these functions we can provide documentation with some minimum contrast ratios defined by some standards (AA, AAA etc.). I don't see much value in adding these to the API since these are only minimum values, not exact targets. Or maybe provide them as constants?

I don't think we should expose an internal representation of HSLuv to the public API unless we really have to. If we can get away with using native Sass colors everywhere it would be simpler for the user.

What are your thoughts? There's a lot going on in this discussion, so I'd be happy to focus on the minimal merge-able implementation here and delay some stuff (e.g. contrast) until the next PR.

@Ptico
Copy link
Contributor Author

Ptico commented Jun 2, 2021

I agree with minimizing an API, though it requires some work on documentation to show users an examples of how to work with sass:math and hsluv

For the rest of new API, the main idea is to work with hsluv as a first class sitizen. It will be useful for designers who prefer to use HSLuv as a main color space, as well as for principal frontend engineers who will build a design systems on top of it.
An internal representation is nothing more than (h:, s:, l:) map. The problem with a SASS color is that we have to round an RGB values to the nearest 0-255 whole number and each conversion from/to RGB loses the color information, so if we define a base color, then do a lot of transformations through the styles, we may have pretty unexpected results.

Also, manipulating an rgb colors with this library makes no sense for me, it's just duplicating what we already have with sass:color and almost doesn't add any value. However, we can always translate an existing sass color with hsluv.create(#123abc)

@apexskier
Copy link
Member

I'm very out of date on SASS syntax, but I have a question about your example:

Why do you need the hsluv() "calls" when using the color variables? I'd expect:

button {
  $baseColor: hsluv(10deg, 80%, 60%);
  $backgroundColor: hsluv.contrast-darken($baseColor, 'AA');
  $borderColor: hsluv.rotate($backgroundColor, 10deg);
  
  color: $baseColor;
  background-color: $backgroundColor;
  border: 1px solid $borderColor;
}

As is, I see two problems:

  • I think people would commonly forget to convert back to a sass color (creating broken css)
  • The utilities can't be used with other color representations (because they expect the internal ('h', 's', 'l') representation) (hsluv.contrast-darken(#ff0000, 'AA'); won't work)

I'd prefer if the hsluv.color/hsluv.create function and ('h', 's', 'l') representation was internal/private to the hsluv implementation. (This assumes it can translate a value of any sass color representation, as implied in @Ptico's last comment.)

e.g.

@function get-hue($color) {
  $raw-hsluv: create($color);
  @return map-get($raw-hsluv, 'h');
}
// usage: hsluv.get-hue(#ff0000)

@function contrast($color, $op: 'darken', $ratio: 'AA') {
  $raw-hsluv: create($color);
  $h: map-get($raw-hsluv, 'h');
  $s: map-get($raw-hsluv, 's');
  $l: map-get($raw-hsluv, 'l');
  // ...
  @return hsluv($raw-hsluv)
}

@boronine
Copy link
Member

boronine commented Jun 4, 2021

I agree with much being said, but I'm a little overwhelmed by how many issues are discussed simultaneously. I'm going to try to summarize our discussion here. Seems that we have 3 proposals (ignoring function names for now):

A. Non-magic minimal version, 6 functions added

$brandColor: #abc123;
$brandH: hsluv.get-hue($brandColor);
$brandS: hsluv.get-saturation($brandColor);
$brandL: hsluv.get-lightness($brandColor);
$compH: ($brandH + 180deg) % 360;

.banner {
    color: hsluv($brandH, $brandS, $brandL);
    background-color: hsluv($compH, $brandS, hsluv.get-darker($brandL, hsluv.AA));
    border: 1px solid hsluv($compH, $brandS, hsluv.get-darker($brandL, hsluv.AA * 1.5));
}

B. Magic version exporting HSLuv internal representation, ~17 functions added

$brandColor: hsluv.create(#abc123);
$compColor: hsluv.rotate($brandColor, 180deg);

.banner {
    color: hsluv.hex($brandColor);
    background-color: hsluv.hex(hsluv.contrast-darken($compColor, 'AA'));
    border: 1px solid hsluv.hex(hsluv.contrast-darken($compColor, 5.5));
}

C. Magic version hiding HSLuv internal representation, ~16 functions added

$brandColor: #abc123;
$compColor: hsluv.rotate($brandColor, 180deg);

.banner {
    color: $brandColor;
    background-color: hsluv.contrast-darken($compColor, 'AA');
    border: 1px solid hsluv.contrast-darken($compColor, 5.5);
}

My preference is option A, then option C, then option B. I agree with @apexskier's arguments to choose option C over option B. @Ptico raises an issue with option C regarding rounding, but can we not avoid rounding if we use CSS rgb(...) constructor rather than hex?

@Ptico
Copy link
Contributor Author

Ptico commented Jun 4, 2021

Hi @boronine @apexskier
My main point actually is to operate HSLuv kind of natively, because I don’t see any value added by this library in operating rgb colors

Would you mind to have a quick text or voice chat somewhere like discord or gitter to better explain an idea?

@boronine
Copy link
Member

boronine commented Jun 5, 2021

@Ptico I'd prefer async communication. Can you explain your idea here? I understood your proposal to be option B above. Are you proposing something different? If so can you draft a list of functions you're proposing?

@Ptico
Copy link
Contributor Author

Ptico commented Jun 5, 2021

I feels like I have a small language barrier to properly explain an idea, but i'll try:

Let's say, we have a team of designer and frontend developer who wants to create a design system for an upcoming web-app from scratch. Buttons, modals, typography, etc.

The designer have to choose one or two main (brand) colors, then also some special, like for errors, dangerous actions, accents, etc. Every color will have a lot of deviations in terms of lightness and saturation and they might be able to quickly add an additional ones. An RGB always was a bad choice because for the majority of people three numbers in a range from 0 to 255 means nothing unless pasted to color picker. It's hard to mix three colors in your head. It's also hard to say what is the difference between two colors in terms of lightness and saturation.

And here is where HSL-kind of colors shines. I know that the range between 120-140deg is a green(ish) color, 80% is pretty saturated and 50% is bright enough (you know this better than me). So even if we already have some brand colors defined in RGB it's simplier to just convert them to HSLuv once and then communicate with this format instead of rgb.

Then we might create some UI patterns, lets say button. We have picked a main color for button, then we might want to have some accent in a form of darker border and to make it great-looking we want a text to be not just white, but bright shade of main color with an accessible contrast. We also want to describe CSS rules just once instead of duplicating the code for each color but make them looks uniform in terms of visual brightness. Here we come to HSLuv

From the designer's perspective the process might look like this:
Designer installs some HSLuv color picker to his favourite design tool, converts existings colors to HSLuv and draws the UI elements. To create a nice button border he just move the L slider to make it darker, and to draw the danger button he just Cmd+C/Cmd+V the main one and move the H slider somewhere to 10deg. Easy, handy, happy days!

Then, the frontend developer should implement this design in SCSS. The process might looks similar to the designer's one (my suggestion) or the designer have to convert all the base colors back to the RGB (where 10deg, 80%, 50% becomes #de3442) gives them to the frontent developer forcing him to continously extract HSLuv values from RGB and likely converting them back and forth loosing the precision and making designer unhappy (and no, using the rgb() constructor will not help, because it's just slightly different representation of the same values).

So, my point is to give the frontend developers a tool for a native HSLuv flow where designers and developers communicate the same language and perform the same operations. Also, in an HSLuv native flow, internal representation is easier to debug because the developer can see the same values as in designs

@Ptico
Copy link
Contributor Author

Ptico commented Jun 5, 2021

I could try to create a real world example and test different approaches there

@boronine
Copy link
Member

boronine commented Jun 5, 2021

Designer installs some HSLuv color picker to his favourite design tool, converts existings colors to HSLuv and draws the UI elements. To create a nice button border he just move the L slider to make it darker, and to draw the danger button he just Cmd+C/Cmd+V the main one and move the H slider somewhere to 10deg. Easy, handy, happy days!

Ok I can imagine a tool like that.

Then, the frontend developer should implement this design in SCSS. The process might looks similar to the designer's one (my suggestion) or the designer have to convert all the base colors back to the RGB (where 10deg, 80%, 50% becomes #de3442) gives them to the frontent developer forcing him to continously extract HSLuv values from RGB and likely converting them back and forth loosing the precision and making designer unhappy

I don't understand this part. The designer can provide HSLuv values and the frontend developer can use hsluv-sass to plug them into SCSS, no? Why would the frontend developer need to convert colors back and forth?

my point is to give the frontend developers a tool for a native HSLuv flow where designers and developers communicate the same language and perform the same operations

The current solution to this problem is for designers to use SASS + hsluv-sass and then pass the .scss file to the frontend developer.

Are you suggesting making some kind of app? I'm not opposed to it though I probably won't have time to contribute. If you want to take a shot at it, I'd happily put it under the https://github.com/hsluv org and link to it from the homepage. If you want to discuss it further perhaps the best place is to open an issue under the main repo: https://github.com/hsluv/hsluv/issues

For this comment thread let's go back to the API question. My summary of the current proposals in this comment above: #9 (comment)

Please let me know which option you choose or if you have another option, please make a list of functions or a code sample to demonstrate it.

@apexskier
Copy link
Member

And here is where HSL-kind of colors shines. I know that the range between 120-140deg is a green(ish) color, 80% is pretty saturated and 50% is bright enough (you know this better than me).

Definitely - this is the clear value of HSLuv and the main point of the project.

Then, the frontend developer should implement this design in SCSS. The process might looks similar to the designer's one (my suggestion) or the designer have to convert all the base colors back to the RGB (where 10deg, 80%, 50% becomes #de3442) gives them to the frontent developer forcing him to continously extract HSLuv values from RGB and likely converting them back and forth loosing the precision and making designer unhappy (and no, using the rgb() constructor will not help, because it's just slightly different representation of the same values).

forcing him to continously extract HSLuv values from RGB and likely converting them back and forth

This point doesn't make sense to me, as I think @boronine also mentioned.

If the developer is able to deal with the built-in sass color values, they should never need to extract HSLuv or RGB values. The only place that needs to happen is inside the hsluv module. My concern with the original proposal in the thread is that you are extracting something. It's not as bad as extracting individual h, s, and l number values, but it is extracting the (h, s, l) map value when you could use color values directly.

loosing the precision

I agree, the loss of precision is the main downside to my suggestion, but I personally don't think it's going to be a huge deal. Designing for the web already means designers already need to be aware of many different factors that cause loss of color fidelity - OS/browser handling, different monitors, etc. I think the value of having a color be a color, and usable in a property directly, is high.

Let's say, we have a team of designer and frontend developer who wants to create a design system for an upcoming web-app from scratch. Buttons, modals, typography, etc.

I like this example, but I think we should also consider non-greenfield projects, which are very common.

Say a team embedded in a large company wants to start using HSLuv, but needs to embed within an existing SASS library where they have a large set of colors already defined as hex colors (e.g. $primary: #212e61. It'd be nice for them to do something like $primary-shadow: hsluv.darken($primary, 0.2) instead of $primary-shadow(hsluv.create($primary), 0.2).

For both cases, I think introducing a new datatype is going to cause unnecessary boilerplate and verboseness as a codebase grows due to the need to convert when declaring, using, and potentially modifying existing colors.

@Ptico
Copy link
Contributor Author

Ptico commented Jun 5, 2021

By the tool I mean hsluv-sass itself

I don't understand this part. The designer can provide HSLuv values and the frontend developer can use hsluv-sass to plug them into SCSS, no? Why would the frontend developer need to convert colors back and forth?

Lets take a look at our examples in a context of HSLuv native flow:

The designer gives us a "danger" color: 10deg, 80%, 50%

Option A:

$dangerH: 10deg;
$dangerS: 80%;
$dangerL: 50%;
$dangerTextL: hsluv.contrast-lighten($dangerL, hsluv.AA);
$dangerBorderL: $dangerL - 20;

.dangerButton {
  background-color: hsluv($dangerH, $dangerS, $dangerL);
  color: hsluv($dangerH, $dangerS, $dangerTextL);
  border-color: hsluv($dangerH, $dangerS, $dangerBorderL);
}

No need for RGB conversions of course, but imagine having 10 different HSLuv colors.

Option C:

$dangerColor: #de3442;

.dangerButton {
  background-color: $dangerColor;
  color: hsluv.contrast-lighten($dangerColor, hsluv.AA);
  border-color: hsluv.darken($dangerColor, 0.2);

Here we:

a) Forced to operate with SASS colors, so the designer would have to convert everything to RGB
b) The only value hsluv-sass adds here is a color-contrast functions, because other operations may be done with sass:color and besides accessability there is no need for HSLuv at all

Option B

$dangerColor: hsluv.create(10deg, 80%, 50%);

.dangerButton {
  background-color: hsluv($dangerColor);
  color: hsluv(hsluv.contrast-lighten($dangerColor, hsluv.AA));
  border-color: hsluv(hsluv.darken($dangerColor, 20%));
}

There is a downside of requirement to apply hsluv() every time we need to assign an HSLuv color to actual CSS, but for a much larger codebases IMO it's much easier to operate with HSLuv colors instead of separate HSLuv values or RGB colors

@apexskier
Copy link
Member

For option C, this is what I'm suggesting:

$dangerColor: hsluv.create(10deg, 80%, 50%);

.dangerButton {
  background-color: $dangerColor;
  color: hsluv.contrast-lighten($dangerColor, hsluv.AA);
  border-color: hsluv.darken($dangerColor, 0.2);
}

No need to convert to RBG.

b) The only value hsluv-sass adds here is a color-contrast functions, because other operations may be done with sass:color and besides accessability there is no need for HSLuv at all

HSLuv adds additional value in that its functions operate on a different geometry than sass:color. I also think interoperability with sass colors is a big benefit.

@Ptico
Copy link
Contributor Author

Ptico commented Jun 5, 2021

I like this example, but I think we should also consider non-greenfield projects, which are very common

Non-greenfield projects are common, the partial usage of HSLuv in them is not, but anyway we already have an API for this kind of usage.

Think of new (optional) API as of tool for the new projects which are going to use HSLuv in all stages of a design pipeline and would not mix SASS colors with HSLuv.

@Ptico
Copy link
Contributor Author

Ptico commented Jun 5, 2021

No need to convert to RBG.

We will need to implement native API internally anyway, would it work if we will let users choose which one they will use?

Like:

@use 'hsluv/native'; // For native operations
//or 
@use 'hsluv/tools'; // For SASS color operations

It will not require too much effort because hsluv/native will be a backend for hsluv-tools

@apexskier
Copy link
Member

I like that idea

@boronine
Copy link
Member

boronine commented Jun 7, 2021

I think the value offered by saving a few keystrokes will be destroyed by wasting time reading though this bloated API, trying to understand what "native" means, trying to understand what happens when you go out of bounds, or getting compilation errors and giving up on hsluv-sass completely. Instead of giving the user 30 different wrappers for hsluv function, it would be better to just explain to the user the meaning of H, S, and L. After they understand it, they never have to reference the documentation again.

I did some tests trying to use rgb and rgba to avoid rounding, but Sass always rounds these to integers so these formats are indeed equivalent to hex. However the difference between two off-by-one hex colors is imperceptible, so I believe rounding is a non-issue in practice. Unless we can come up with a practical scenario where rounding is problem, all "native" API options should be rejected, leaving us with minimal API (option A) and sass:color clone (option C).

@Ptico
Copy link
Contributor Author

Ptico commented Jun 7, 2021

Ok, here is another suggestion: let’s have a minimal set of tools, like color contrast and component extraction and collection of helpers in an external, third-party package. Would it be fair to mention it in a readme?

@boronine
Copy link
Member

boronine commented Jun 7, 2021

@Ptico before we consider making another repo, why don't we take stock of the discussion.

You mention 3 things:

  1. color contrast
  2. component extraction (rgb -> hsluv)
  3. helper functions (sass:color clone)

I think all of us in this discussion agree that 1 and 2 would add value to this repo. Am I right? I propose we implement those first, release them as v2.1.0, then open another issue for helper functions, summarizing the relevant discussion from this thread. Let's make incremental progress.

At the end of the day I think @apexskier should make the call as the original creator of this port. I will merely provide my opinions.

@Ptico
Copy link
Contributor Author

Ptico commented Jun 12, 2021

@boronine yeah, that’s what I meant. Also, I’m thinking about to update my company’s design system and then make another proposal based on a code extraction from the real usecase

@boronine
Copy link
Member

@Ptico ah okay, I misread your comment. Great that we agree!

I’m thinking about to update my company’s design system and then make another proposal based on a code extraction from the real usecase

This sounds like a great idea!

@badfeather
Copy link

badfeather commented Jan 21, 2023

Have there been any updates on this pull request and/or discussion? I am very interested in having more SASS hsluv methods to work with in generating color schemes for my projects, particularly the ability to create an hsluv color object and HSL properties from a HEX color. Cool project!

@boronine
Copy link
Member

@badfeather I think RGB/hex -> HSLuv conversion can be valuable to expose. When generating palettes from scratch there is not much need for it, but when working around a color specified elsewhere, e.g. a brand color, it may be useful. I would make a separate issue for it.

@badfeather
Copy link

@boronine Yes, exactly. It would be very useful to take a brand's colors and convert them to HSLuv colors in order to generate shades. I will create a separate issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants