Liking cljdoc? Tell your friends :D

Tutorial: Working with Colors in Clojure

Use these examples to learn the main jon/color-tools workflows.

Setup

Add the library to your project and require it:

(require '[jon.color-tools :as color])

Chapter 1: Color Formats and Conversions

Understanding Color Formats

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]

Basic Conversions

Use universal conversion functions for application code. Use format-specific functions when you know the input format.

Universal Conversion Functions

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 Functions

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]

Validation

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

Chapter 2: Color Manipulation

Lightness and Darkness

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"

Saturation

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"

Hue Shifting

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"

Chapter 3: Accessibility and Contrast

Understanding Contrast Ratios

WCAG defines minimum contrast ratios for accessibility:

  • AA Normal: 4.5:1 minimum
  • AA Large: 3:1 minimum
  • AAA Normal: 7:1 minimum
;; 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

Accessibility Checking

(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

Finding Accessible Colors

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

Generating Accessible Theme Tokens

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"
;    ...}

Chapter 4: Palette Generation

Color Harmony

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"]

Monochromatic Palettes

Create palettes using variations of a single hue:

(color/monochromatic "#3498db" 5)
;=> ["#003d66" "#066aad" "#1096ef" "#5db4ee" "#a6d4f2"]

Theme Generation

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

Chapter 5: Color Analysis

Basic Properties

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

Vibrancy and Saturation

(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%

Color Distance

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

Chapter 6: Real-World Examples

Example 1: Accessible App Theme

(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"
;    ...}

Example 2: Accessibility Checker

(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})

Example 3: Dynamic Color Scaling

(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"]

Next Steps

  • Explore the complete API reference
  • Try building your own color utilities
  • Experiment with different color harmonies
  • Build accessibility checking into your applications
  • Create dynamic themes based on user preferences

Tips and Best Practices

  1. Always validate user input when accepting colors from external sources
  2. Consider accessibility from the start of your design process
  3. Use generated theme tokens for app surfaces, text, focus, and borders
  4. Test color combinations across different devices and lighting conditions
  5. Provide fallbacks for color-blind users
  6. Cache expensive operations like palette generation when possible

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close