This tutorial will walk you through the main features of jon/color-tools with practical examples.
First, add the library to your project and require it:
(require '[jon.color-tools :as color])
Colors can be represented in many ways. This library supports the most common formats:
;; HEX - Web standard, strings starting with #
"#ff5733" ; Full 6-digit
"#f53" ; Short 3-digit (expanded to #ff5533)
;; RGB - Red, Green, Blue values 0-255
[255 87 51]
;; RGBA - RGB with Alpha channel 0-1
[255 87 51 0.8]
;; CSS RGB - CSS color strings with flexible formatting
"rgb(255, 87, 51)" ; Comma-separated (standard)
"rgb(255 87 51)" ; Space-separated (modern CSS)
"RGB( 255 , 87 , 51 )" ; Case-insensitive, extra spaces
;; CSS RGBA - CSS color strings with alpha channel
"rgba(255, 87, 51, 1)" ; Comma-separated with alpha
"rgba(255 87 51 0.8)" ; Space-separated with alpha
"rgba(255, 87, 51, .5)" ; Decimal alpha values
;; HSL - Hue (0-360), Saturation (0-100), Lightness (0-100)
[9 100 60]
;; HSV - Hue (0-360), Saturation (0-100), Value (0-100)
[9 80 100]
The library provides two approaches for conversions: format-specific functions and universal conversion functions.
These functions work with any color input type:
;; Convert any color type to RGB
(color/->rgb "#ff5733") ; hex to RGB
;=> [255 87 51]
(color/->rgb "rgb(255, 87, 51)") ; CSS RGB to RGB
;=> [255 87 51]
(color/->rgb "rgba(255, 87, 51, 0.8)") ; CSS RGBA to RGB (ignores alpha)
;=> [255 87 51]
(color/->rgb [255 87 51]) ; RGB passthrough
;=> [255 87 51]
;; Convert any color type to HEX
(color/->hex [255 87 51]) ; RGB to hex
;=> "#ff5733"
(color/->hex "rgb(255, 87, 51)") ; CSS RGB to hex
;=> "#ff5733"
(color/->hex "#ff5733") ; hex passthrough
;=> "#ff5733"
;; Convert any color type to HSL
(color/->hsl "#ff5733") ; hex to HSL
;=> [9 100 60]
(color/->hsl "rgb(255, 87, 51)") ; CSS RGB to HSL
;=> [9 100 60]
Traditional format-specific conversions are still available:
;; HEX to RGB
(color/hex->rgb "#ff5733")
;=> [255 87 51]
;; RGB to HEX
(color/rgb->hex [255 87 51])
;=> "#ff5733"
;; Parse CSS color strings
(color/parse-css-rgb "rgb(255, 87, 51)")
;=> [255 87 51]
(color/parse-css-rgba "rgba(255, 87, 51, 0.8)")
;=> [255 87 51 0.8]
;; RGB to HSL for color manipulation
(color/rgb->hsl [255 87 51])
;=> [9 100 60]
;; Chain conversions
(-> "#3498db"
color/hex->rgb
color/rgb->hsl)
;=> [204 70 53]
Always validate colors when accepting user input:
(color/valid-hex? "#ff5733") ;=> true
(color/valid-hex? "invalid") ;=> false
(color/valid-rgb? [255 87 51]) ;=> true
(color/valid-rgb? [300 87 51]) ;=> false (out of range)
(color/valid-hsl? [204 70 53]) ;=> true
Adjust the lightness of colors. The functions work with any input format and preserve the input type:
(def blue "#3498db")
(def blue-css "rgb(52, 152, 219)")
;; Make it 20% lighter - works with any format
(color/lighten blue 0.2) ; hex input -> hex output
;=> "#5dade2"
(color/lighten blue-css 0.2) ; CSS RGB input -> hex output
;=> "#5dade2"
(color/lighten [52 152 219] 0.2) ; RGB vector input -> RGB vector output
;=> [93 173 226]
;; Make it 30% darker
(color/darken blue 0.3)
;=> "#2471a3"
(color/darken blue-css 0.3) ; CSS input also works
;=> "#2471a3"
;; Chain operations - seamless format handling
(-> blue
(color/lighten 0.1)
(color/darken 0.05))
;=> "#4fa3d6"
Control the intensity of colors. These functions also work with CSS color strings:
(def orange "#ff8c00")
(def orange-css "rgb(255, 140, 0)")
;; More vivid - works with any input format
(color/saturate orange 0.2) ; hex input
;=> "#ff7700"
(color/saturate orange-css 0.2) ; CSS RGB input
;=> "#ff7700"
;; More muted
(color/desaturate orange 0.4)
;=> "#cc9633"
(color/desaturate "rgba(255, 140, 0, 0.8)" 0.4) ; CSS RGBA input
;=> "#cc9633"
;; Complete desaturation = grayscale
(color/desaturate orange 1.0)
;=> "#999999"
Rotate colors around the color wheel:
(def red "#e74c3c")
;; Shift 60 degrees toward orange
(color/adjust-hue red 60)
;=> "#e7e73c"
;; Complement (180 degrees)
(color/adjust-hue red 180)
;=> "#3ce7e7"
;; Or use the convenience function
(color/complementary red)
;=> "#3ce7e7"
WCAG defines minimum contrast ratios for accessibility:
;; Check contrast ratio
(color/contrast-ratio "#000000" "#ffffff")
;=> 21.0 ; Perfect contrast
(color/contrast-ratio "#3498db" "#ffffff")
;=> 3.14 ; Not enough for AA normal text
(color/contrast-ratio "#2c3e50" "#ffffff")
;=> 12.63 ; Excellent contrast
(def background "#f8f9fa")
(def text-color "#6c757d")
;; Check different standards
(color/accessible? background text-color :aa)
;=> false
(color/accessible? background text-color :aa-large)
;=> true
(color/accessible? background text-color :aaa)
;=> false
Automatically find accessible alternatives:
(def bg "#3498db")
(def desired-text "#e74c3c")
;; Find an accessible version of the text color
(color/find-accessible-color bg desired-text :aa)
;=> "#b71c1c" ; Darker red that meets AA standards
;; For large text (lower requirements)
(color/find-accessible-color bg desired-text :aa-large)
;=> "#c62828" ; Slightly lighter option
When you need a real UI theme, start with accessible-theme instead of
manually picking every foreground color. It generates semantic tokens,
foreground pairs, a focus ring, borders, and a 50-900 scale from one brand
color.
(def theme
(color/accessible-theme "#3498db" {:mode :light :level :aa}))
(:background theme)
;=> "#f7fbfe"
(:primary theme)
;=> "#3498db"
(:on-primary theme)
;=> "#000000"
(color/accessible? (:primary theme) (:on-primary theme) :aa)
;=> true
Use :mode :dark for dark interfaces and :level :aaa when you want stricter
normal text contrast.
(color/accessible-theme "#ff7a59" {:mode :dark :level :aaa})
;=> {:mode :dark
; :level :aaa
; :background "#1f0f0b"
; :primary "#ff7a59"
; ...}
For frontend apps, export the generated tokens as CSS custom properties:
(color/theme->css-vars theme {:prefix "--brand-"})
;=> {"--brand-background" "#f7fbfe"
; "--brand-primary" "#3498db"
; "--brand-on-primary" "#000000"
; "--brand-scale-500" "#3498db"
; ...}
Generate harmonious color combinations based on color theory:
(def base-color "#e74c3c")
;; Complementary (opposite on color wheel)
(color/complementary base-color)
;=> "#3ce7e7"
;; Triadic (three colors forming triangle)
(color/triadic base-color)
;=> ["#4ce73c" "#3c4ce7"]
;; Split complementary (base + two colors near complement)
(color/split-complementary base-color)
;=> ["#3ce77b" "#3c7be7"]
;; Analogous (adjacent colors)
(color/analogous base-color 4 30) ; 4 colors, 30° apart
;=> ["#e7743c" "#e7ac3c" "#e7e43c" "#afe73c"]
Create palettes using variations of a single hue:
(color/monochromatic "#3498db" 5)
;=> ["#2980b9" "#3498db" "#5dade2" "#85c1e9" "#aed6f1"]
Use the palette generator for different strategies:
;; Generate a triadic theme
(color/generate-palette :triadic "#e74c3c")
;=> ["#4ce73c" "#3c4ce7"]
;; Generate analogous theme with custom options
(color/generate-palette :analogous "#3498db" {:count 5 :angle 20})
;=> ["#3498db" "#34b8db" "#34dbd8" "#34dbaf" "#34db87"]
;; Random palette for experimentation
(color/generate-palette :random nil {:count 3})
;=> ["#a47f3c" "#2e8b57" "#ff6347"]
Analyze color characteristics:
(def navy "#2c3e50")
(def coral "#ff6b6b")
;; Brightness (perceived luminance)
(color/brightness navy) ;=> 60 (dark)
(color/brightness coral) ;=> 122 (medium)
;; Boolean checks
(color/dark? navy) ;=> true
(color/light? coral) ;=> false
(color/warm? coral) ;=> true (red family)
(color/cool? navy) ;=> true (blue family)
(def vibrant "#ff1744") ; Hot pink
(def muted "#8d6e63") ; Brown grey
(color/vibrant? vibrant) ;=> true
(color/muted? muted) ;=> true
;; Custom thresholds
(color/vibrant? "#ff6b6b" 70) ; 70% saturation threshold
;=> false
Compare colors mathematically:
;; Euclidean distance in RGB space
(color/color-distance "#ff0000" "#ff0011")
;=> 17.0
;; Check similarity within threshold
(color/similar? "#ff0000" "#ff0011" 20)
;=> true
(color/similar? "#ff0000" "#00ff00" 50)
;=> false ; Too different
(defn build-theme-css [brand-color mode]
(-> (color/accessible-theme brand-color {:mode mode :level :aa})
(color/theme->css-vars {:prefix "--app-"})))
(build-theme-css "#9b59b6" :light)
;=> {"--app-background" "#fbf8fc"
; "--app-on-background" "#111827"
; "--app-primary" "#9b59b6"
; "--app-on-primary" "#ffffff"
; "--app-focus-ring" "#b6586b"
; "--app-scale-500" "#9b59b6"
; ...}
(defn check-color-accessibility [bg-color text-colors]
(for [text-color text-colors]
(let [ratio (color/contrast-ratio bg-color text-color)]
{:color text-color
:ratio ratio
:aa-normal (>= ratio 4.5)
:aa-large (>= ratio 3.0)
:aaa-normal (>= ratio 7.0)})))
(check-color-accessibility "#ffffff"
["#000000" "#333333" "#666666" "#999999"])
;=> ({:color "#000000", :ratio 21.0, :aa-normal true, :aa-large true, :aaa-normal true}
; {:color "#333333", :ratio 12.63, :aa-normal true, :aa-large true, :aaa-normal true}
; {:color "#666666", :ratio 5.74, :aa-normal true, :aa-large true, :aaa-normal false}
; {:color "#999999", :ratio 2.85, :aa-normal false, :aa-large false, :aaa-normal false})
(defn create-color-scale [start-color end-color steps]
(for [i (range steps)]
(let [ratio (/ i (dec steps))]
(color/mix start-color end-color ratio))))
(create-color-scale "#3498db" "#e74c3c" 5)
;=> ("#3498db" "#5d7bc9" "#855fb7" "#ad42a4" "#e74c3c")
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |