{"id":16577027,"url":"https://github.com/nvlang/egal","last_synced_at":"2025-10-29T04:32:07.349Z","repository":{"id":209772605,"uuid":"724890194","full_name":"nvlang/egal","owner":"nvlang","description":"Uniform color palettes.","archived":false,"fork":false,"pushed_at":"2024-12-16T09:57:21.000Z","size":17627,"stargazers_count":3,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-01T22:21:46.266Z","etag":null,"topics":["color","color-palette","color-theme","colors"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nvlang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-29T02:02:28.000Z","updated_at":"2024-09-17T16:27:14.000Z","dependencies_parsed_at":"2024-06-22T13:33:33.350Z","dependency_job_id":"233a4290-2914-4fa5-b23e-2dfb078aca06","html_url":"https://github.com/nvlang/egal","commit_stats":null,"previous_names":["nvlang/egal"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvlang%2Fegal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvlang%2Fegal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvlang%2Fegal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvlang%2Fegal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nvlang","download_url":"https://codeload.github.com/nvlang/egal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238768423,"owners_count":19527197,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["color","color-palette","color-theme","colors"],"created_at":"2024-10-11T22:09:39.582Z","updated_at":"2025-10-29T04:32:07.342Z","avatar_url":"https://github.com/nvlang.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cbr\u003e\n\u003cdiv align=\"center\"\u003e\n\u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/nvlang/egal/main/res/logotype-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://raw.githubusercontent.com/nvlang/egal/main/res/logotype-light.svg\"\u003e\n    \u003cimg alt=\"Logotype\" src=\"https://raw.githubusercontent.com/nvlang/egal/main/res/logotype-light.svg\" width=\"70%\"\u003e\n\u003c/picture\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cdiv\u003e\n\n[\n\u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://img.shields.io/github/v/tag/nvlang/egal?style=flat-square\u0026logo=GitHub\u0026logoColor=a3acb7\u0026label=\u0026labelColor=21262d\u0026color=21262d\u0026filter=@nvl/egal@*\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://img.shields.io/github/v/tag/nvlang/egal?style=flat-square\u0026logo=GitHub\u0026logoColor=24292f\u0026label=\u0026labelColor=eaeef2\u0026color=eaeef2\u0026filter=@nvl/egal@*\"\u003e\n    \u003cimg alt=\"GitHub version tag\" src=\"https://img.shields.io/github/v/tag/nvlang/egal?style=flat-square\u0026logo=GitHub\u0026logoColor=24292f\u0026label=\u0026labelColor=eaeef2\u0026color=eaeef2\u0026filter=@nvl/egal@*\"\u003e\n\u003c/picture\u003e\n](https://github.com/nvlang/egal)\n[\n\u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://img.shields.io/badge/@nvl/egal-_?style=flat-square\u0026logo=npm\u0026logoColor=a3acb7\u0026labelColor=21262d\u0026color=21262d\u0026logoSize=auto)\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://img.shields.io/badge/@nvl/egal-_?style=flat-square\u0026logo=npm\u0026logoColor=24292f\u0026labelColor=eaeef2\u0026color=eaeef2\u0026logoSize=auto)\"\u003e\n    \u003cimg alt=\"NPM package name\" src=\"https://img.shields.io/badge/@nvl/egal-_?style=flat-square\u0026logo=npm\u0026logoColor=24292f\u0026labelColor=eaeef2\u0026color=eaeef2\u0026logoSize=auto)\"\u003e\n\u003c/picture\u003e\n](https://npmjs.com/@nvl/egal)\n[\n\u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://img.shields.io/badge/@nvl/egal-_?style=flat-square\u0026labelColor=21262d\u0026color=21262d\u0026logo=jsr\u0026logoColor=a3acb7\u0026logoSize=auto\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://img.shields.io/badge/@nvl/egal-_?style=flat-square\u0026labelColor=eaeef2\u0026color=eaeef2\u0026logo=jsr\u0026logoColor=24292f\u0026logoSize=auto\"\u003e\n    \u003cimg alt=\"JSR package name\" src=\"https://img.shields.io/badge/@nvl/egal-_?style=flat-square\u0026labelColor=eaeef2\u0026color=eaeef2\u0026logo=jsr\u0026logoColor=24292f\u0026logoSize=auto\"\u003e\n\u003c/picture\u003e\n](https://jsr.io/@nvl/egal)\n[\n\u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://jsr.io/badges/@nvl/egal/score?style=flat-square\u0026labelColor=21262d\u0026color=21262d\u0026logoColor=a3acb7\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://jsr.io/badges/@nvl/egal/score?style=flat-square\u0026labelColor=eaeef2\u0026color=eaeef2\u0026logoColor=24292f\"\u003e\n    \u003cimg alt=\"JSR score\" src=\"https://jsr.io/badges/@nvl/egal/score?style=flat-square\u0026labelColor=eaeef2\u0026color=eaeef2\u0026logoColor=24292f\"\u003e\n\u003c/picture\u003e\n](https://jsr.io/@nvl/egal)\n[\n\u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://img.shields.io/codecov/c/github/nvlang/egal?flag=egal\u0026style=flat-square\u0026logo=codecov\u0026label=\u0026logoColor=a3acb7\u0026labelColor=21262d\u0026color=21262d\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://img.shields.io/codecov/c/github/nvlang/egal?flag=egal\u0026style=flat-square\u0026logo=codecov\u0026label=\u0026logoColor=24292f\u0026labelColor=eaeef2\u0026color=eaeef2\"\u003e\n    \u003cimg alt=\"CodeCov coverage\" src=\"https://img.shields.io/codecov/c/github/nvlang/egal?flag=egal\u0026style=flat-square\u0026logo=codecov\u0026label=\u0026logoColor=24292f\u0026labelColor=eaeef2\u0026color=eaeef2\"\u003e\n\u003c/picture\u003e\n](https://codecov.io/gh/nvlang/egal)\n\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cbr\u003e\n\n\u003e [!NOTE]\n\u003e **Getting started**: Refer to the README of the\n\u003e [standalone package](./packages/egal/README.md#getting-started),\n\u003e [PostCSS plugin](./packages/postcss-egal/README.md#getting-started), or\n\u003e [Lightning CSS plugin](./packages/lightningcss-plugin-egal/README.md#getting-started),\n\u003e depending on your use case.\n\n## Why?\n\n### Problem\n\nColor spaces like OkLCh and HCT aim to be perceptually uniform, and they can be\nincredibly useful if consistency of lightness across hues is important. However,\nit can be difficult to ensure consistency of chroma (saturation) across hues.\nThis is because not all hues can deliver the same chroma at a given lightness.\n\nFor example, if you take the color `oklch(50% 0.275 280)`\n![#5627f8](https://placehold.co/12/5627f8/0000), you get a very saturated\npurple. However, if you shift the hue to 200, you'll find that an sRGB monitor\ncannot truly display the color `oklch(50% 0.275 200)`, and instead will silently\nfall back to `oklch(50% 0.0848 200)`\n![#017176](https://placehold.co/12/017176/0000).\n\n### Idea\n\nNow, how can we make colors of different hues but equal lightness appear equally\nsaturated? One way, of course, is to set the chroma to zero, but this would just\nyield the same shade of gray independent of hue. Alternatively, one can try to\nfind the largest chroma value that all the different hues can still deliver at\nthe given lightness. That's precisely what _égal_ (IPA /e.ɡal/ — from French,\nmeaning \"equal\" or \"indifferent\") does. It maps the absolute scale of chroma\nembedded in OkLCh into a scale that is relative to the aforementioned maximal\nchroma.\n\n\u003cbr\u003e\n\u003cdiv align=\"center\"\u003e\n\u003cfigure\u003e\n    \u003cpicture\u003e\n        \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"res/oklch-vs-egal-fixed-lightness-dark.svg\"\u003e\n        \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"res/oklch-vs-egal-fixed-lightness-light.svg\"\u003e\n        \u003cimg alt=\"Graph titled 'oklch( 0.5, [0,0.35], [0,360) )', with hue as x-axis and chroma as y-axis. The hue ranges from 0 to 360 degrees, and the chroma ranges from 0 to 0.35 on the left side, which is labeled 'oklch', and from 0% to 400% on the right side, which is labeled 'egal'. At coordinate (x,y), the graph shows a pixel of color `oklch(0.5,y,x)`, where y is understood with the oklch scale for chroma. Only colors which are visible in sRGB are shown in the graph, yielding a shape with a colorful but irregular top with occasional spikes, and a monotonous gray bottom. A dashed, white, straight horizontal line is drawn across this colorful shape around y coordinate 0.085 in the oklch scale and 100% in the egal scale, and intersects with the global minimum of the shape, around hue 200 degrees.\" src=\"res/oklch-vs-egal-fixed-lightness-light.svg\" width=\"80%\"\u003e\n    \u003c/picture\u003e\n    \u003cbr\u003e\n    \u003cfigcaption\u003e\n        \u003ci\u003eAt 50% lightness, the bottleneck for chroma is around hue 200 degrees.\u003c/i\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003c/div\u003e\n\u003cbr\u003e\n\nThe tradeoff here is that we lose consistency of chroma values across\nlightnesses:\n\n\u003cbr\u003e\n\u003cdiv align=\"center\"\u003e\n\u003cfigure\u003e\n    \u003cpicture\u003e\n        \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"res/oklch-vs-egal-fixed-hue-dark.svg\"\u003e\n        \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"res/oklch-vs-egal-fixed-hue-light.svg\"\u003e\n        \u003cimg alt=\"Graph titled 'oklch( [0,1], [0,0.35], 0 )', with lightness as x-axis and chroma as y-axis (both using the OkLCh scale). A pyramid-like shape colored bright magenta at the top, black at the bottom left corner, and white at the bottom right corner, with smooth color gradients between the corners, illustrates which colors in the range referred to by the title are visible in the sRGB gamut. Separately, a white line starting at the bottom left corner goes straight toward a peak at around lightness 80 and chroma 0.1, and then goes to the bottom right corner in a staight line from there. This line illustrates the chroma value on the OkLCh chroma scale that a chroma value of 100% in the egal reparametrization corresponds to as a function of the lightness.\" src=\"res/oklch-vs-egal-fixed-hue-light.svg\" width=\"80%\"\u003e\n    \u003c/picture\u003e\n    \u003cbr\u003e\n    \u003cfigcaption\u003e\n        \u003ci\u003eWhat a chroma of 100% means for egal depends on the lightness.\u003c/i\u003e\n    \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003c/div\u003e\n\u003cbr\u003e\n\nHowever, given that the consistency of chroma across lightness was still\nvulnerable to being broken by fallbacks arising from the limitations of the sRGB\n(or P3, Rec2020, etc.) color gamut, this tradeoff might not be as bad as it\nseems, depending on the use case.\n\n### In Detail\n\nAs mentioned before, the core idea behind égal is as follows:\n\n\u003e For a given lightness, find the largest chroma such that any hue will be still\n\u003e able to deliver that chroma at the given lightness.\n\nNow, this can be generalized into the following:\n\n\u003e For a given lightness $\\ell$, provide a _linear_ mapping\n\u003e $f_\\ell\\colon[0,\\infty)\\to[0,\\infty)$ such that $f_\\ell(0) = 0$ and\n\u003e $f_\\ell(100)$ equals the largest chroma such that, at the given lightness, any\n\u003e hue will still be able to deliver that chroma.\n\nThese functions $f_\\ell$ are then used to define a mapping into the input space\nof OkLCh as follows:\n\n$$\n\\begin{aligned}\n\\texttt{egal} \u0026\\colon [0,100]\\times[0,\\infty)\\times[0,360) \\to [0,100]\\times[0,\\infty)\\times[0,360), \\\\\n\\texttt{egal} \u0026\\colon \\langle \\ell, c, h \\rangle \\mapsto \\langle \\ell, f_\\ell(c), h \\rangle.\n\\end{aligned}\n$$\n\nNote that these functions are sensitive to the color gamut that we are\ntargeting, since the maximum chroma values directly depend on that gamut. This\nis because the gamut is what specifies when a color is no longer considered\ndisplayable. For example, P3 monitors are able to display more saturated colors\nthan sRGB monitors, and therefore the maximum chroma values for P3 are larger\nthan those for sRGB. Because of this, the target gamut can be specified as an\noption passed to the egal function.\n\nFurthermore, if you prefer to use HCT instead of OkLCh behind the scenes, that's\nalso supported.\n\n## Other Resources\n\n### Software\n\n-   [color.js](https://github.com/color-js/color.js): \"Color conversion \u0026\n    manipulation library by the editors of the CSS Color specifications\"\n-   [ColorAide](https://github.com/facelessuser/coloraide): Object-oriented\n    color manipulation library written purely in Python.\n-   [OKLCH Color Picker \u0026 Converter](https://oklch.com/): Online OkLCh color\n    picker and converter.\n-   [HCT Color Converter](https://www.hct-color-converter.com): Online HCT color\n    converter.\n-   [HCT Color Picker](https://www.figma.com/community/plugin/1227923985322908257/hct-color-picker):\n    Figma plugin for picking colors in the HCT color space.\n-   [Material Color Utilities](https://github.com/material-foundation/material-color-utilities):\n    Google's color libraries for Material Design.\n\n### Articles\n\n-   [Color Appearance Model](https://en.wikipedia.org/wiki/Color_appearance_model):\n    Wikipedia article on color appearance models.\n-   [The Science of Color \u0026 Design](https://material.io/blog/science-of-color-design):\n    Google's blog post on the HCT color space.\n-   [Exploring Tonal Palettes](https://facelessuser.github.io/coloraide/playground/?notebook=https%3A%2F%2Fgist.githubusercontent.com%2Ffacelessuser%2F0235cb0fecc35c4e06a8195d5e18947b%2Fraw%2F3ca56c388735535de080f1974bfa810f4934adcd%2Fexploring-tonal-palettes.md):\n    A Jupyter notebook exploring tonal palettes using the ColorAide library, by\n    the author of the library.\n\n### Discussions\n\n-   [HCT Color Space](https://news.ycombinator.com/item?id=37308278): Hacker\n    News discussion on the HCT color space, involving its creator.\n\n### Color Systems\n\n-   Spot color systems: [PANTONE](https://en.wikipedia.org/wiki/Pantone),\n    [RAL](https://en.wikipedia.org/wiki/RAL_colour_standard),\n    [ANPA](https://en.wikipedia.org/w/index.php?title=Spot_color\u0026oldid=1180172085#Classification)\n-   Widespread digital color systems:\n    [X11](https://en.wikipedia.org/wiki/X11_color_names),\n    [web colors](https://en.wikipedia.org/wiki/Web_colors)\n-   Specialized color systems: [Adobe Spectrum](https://spectrum.adobe.com/page/color-palette/),\n    [Apple HIG](https://developer.apple.com/design/human-interface-guidelines/color),\n    [Microsoft FluentUI](https://developer.microsoft.com/fluentui#/styles/web/colors/theme-slots),\n    [TailwindCSS](https://tailwindcss.com/docs/customizing-colors),\n    [IBM Carbon](https://carbondesignsystem.com/guidelines/color/tokens),\n    [VMware Clarity](https://clarity.design/documentation/color),\n    [Google Material](https://m3.material.io/styles/color/the-color-system/tokens),\n    [Github Primer](https://primer.style/foundations/color), etc.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnvlang%2Fegal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnvlang%2Fegal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnvlang%2Fegal/lists"}