Use these examples to learn the main jon/color-tools workflows.
Add the library to your project and require it:
(require '[jon.color-tools :as color])
The library accepts the color formats Clojure apps tend to pass around:
;; 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]
Use universal conversion functions for application code. Use format-specific functions when you know the input format.
These functions accept any supported color input:
;; 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
;=> [11 100 60]
(color/->hsl "rgb(255, 87, 51)") ; CSS RGB to HSL
;=> [11 100 60]
Format-specific conversions stay useful at API boundaries:
;; 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])
;=> [11 100 60]
;; Chain conversions
(-> "#3498db"
color/hex->rgb
color/rgb->hsl)
;=> [204 70 53]
Validate colors at user-input boundaries:
(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 lightness while preserving the input type:
(def blue "#3498db")
(def blue-css "rgb(52, 152, 219)")
;; Make it 20% lighter
(color/lighten blue 0.2) ; hex input -> hex output
;=> "#8ac4ea"
(color/lighten blue-css 0.2) ; CSS RGB input -> hex output
;=> "#8ac4ea"
(color/lighten [52 152 219] 0.2) ; RGB vector input -> RGB vector output
;=> [138 196 234]
;; Make it 30% darker
(color/darken blue 0.3)
;=> "#124364"
(color/darken blue-css 0.3) ; CSS input
;=> "#124364"
;; Chain operations
(-> blue
(color/lighten 0.1)
(color/darken 0.05))
;=> "#49a3df"
Control saturation. These functions also accept CSS color strings:
(def orange "#ff8c00")
(def orange-css "rgb(255, 140, 0)")
;; More vivid
(color/saturate orange 0.2) ; hex input
;=> "#ff8c00" ; already fully saturated
(color/saturate orange-css 0.2) ; CSS RGB input
;=> "#ff8c00"
;; More muted
(color/desaturate orange 0.4)
;=> "#cc8733"
(color/desaturate "rgba(255, 140, 0, 0.8)" 0.4) ; CSS RGBA input
;=> "#cc8733"
;; Complete desaturation = grayscale
(color/desaturate orange 1.0)
;=> "#808080"
Rotate colors around the color wheel:
(def red "#e74c3c")
;; Shift 60 degrees toward orange
(color/adjust-hue red 60)
;=> "#d6e73c"
;; Complement (180 degrees)
(color/adjust-hue red 180)
;=> "#3cd6e7"
;; Or use the convenience function
(color/complementary red)
;=> "#3cd6e7"
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.15 ; Not enough for AA normal text
(color/contrast-ratio "#2c3e50" "#ffffff")
;=> 10.98 ; 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
Find accessible alternatives by adjusting lightness:
(def bg "#3498db")
(def desired-text "#e74c3c")
;; Find an accessible version of the text color
(color/find-accessible-color bg desired-text :aa)
;=> "#000000" ; Black is the first candidate that meets AA
;; For large text (lower requirements)
(color/find-accessible-color bg desired-text :aa-large)
;=> "#000000" ; Black passes the large-text threshold too
Use accessible-theme instead of hand-picking foreground colors. 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)
;=> "#3cd6e7"
;; Triadic (three colors forming triangle)
(color/triadic base-color)
;=> ["#3ce74d" "#4d3ce7"]
;; Split complementary (base + two colors near complement)
(color/split-complementary base-color)
;=> ["#3ce7b4" "#3c6fe7"]
;; Analogous (adjacent colors)
(color/analogous base-color 4 30) ; 4 colors, 30° apart
;=> ["#e7a23c" "#d6e73c" "#80e73c" "#3ce74d"]
Create palettes using variations of a single hue:
(color/monochromatic "#3498db" 5)
;=> ["#003d66" "#066aad" "#1096ef" "#5db4ee" "#a6d4f2"]
Use the palette generator for different strategies:
;; Generate a triadic theme
(color/generate-palette :triadic "#e74c3c")
;=> ["#3ce74d" "#4d3ce7"]
;; Generate analogous theme with custom options
(color/generate-palette :analogous "#3498db" {:count 5 :angle 20})
;=> ["#3360db" "#3e33db" "#7633db" "#ae33db" "#db33d0"]
;; Random palette for experimentation
(color/generate-palette :random nil {:count 3})
;; Returns three random hex colors, different on each call
Analyze color characteristics:
(def navy "#2c3e50")
(def coral "#ff6b6b")
;; Brightness (perceived luminance)
(color/brightness navy) ;=> 59 ; dark
(color/brightness coral) ;=> 151 ; light
;; Boolean checks
(color/dark? navy) ;=> true
(color/light? coral) ;=> true
(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
;=> true ; saturation is 100%
Measure RGB distance between two colors:
;; 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]
{:color text-color
;; Rounded for display. Use accessible? for pass/fail checks.
:ratio (color/contrast-ratio bg-color text-color)
:aa-normal (color/accessible? bg-color text-color :aa)
:aa-large (color/accessible? bg-color text-color :aa-large)
:aaa-normal (color/accessible? bg-color text-color :aaa)}))
(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" "#6185b3" "#8e728c" "#ba5f64" "#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 |