Color Contrast Checker — Swift source
Check WCAG 2.2 contrast ratio between any two colors with AA / AAA pass-fail for normal and large text, plus a live preview. For accessible, on-brand design.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Color contrast — WCAG 2.2 color contrast math.
// Language: Swift 5.9 — standard library + Foundation (Darwin pow; no third-party color packages).
// Source: CosmoDev polyglot showcase port of the `contrast` tool — canonical
// src/lib/color.ts + this tool's python.py / rust.rs (same inputs -> same outputs).
// License: display source — part of CosmoDev's polyglot tool pages.
import Foundation
/// An 8-bit sRGB color triple.
struct Rgb: Equatable {
var r: Int
var g: Int
var b: Int
}
/// Parses a CSS-style hex color into an `Rgb` value.
///
/// Accepts an optional leading `#`, a 3-digit shorthand (`#abc`), or the
/// 6-digit form (`#aabbcc`). Returns `nil` when the string is not a valid
/// hex color, so callers can distinguish "invalid" from a real color.
func hexToRgb(_ color: String) -> Rgb? {
// Trim surrounding whitespace, then drop exactly one leading '#'
// (removeFirst takes a single occurrence, not all of them).
var h = color.trimmingCharacters(in: .whitespaces)
if h.hasPrefix("#") { h.removeFirst() }
// Expand CSS shorthand: each hex digit is doubled ("abc" -> "aabbcc").
let expanded: String
switch h.count {
case 3: expanded = h.map { "\($0)\($0)" }.joined()
case 6: expanded = h
default: return nil
}
// Six validated hex digits always fit in UInt32; bit-slice into channels.
guard expanded.allSatisfy(\.isHexDigit),
let n = UInt32(expanded, radix: 16) else {
return nil
}
return Rgb(r: Int((n >> 16) & 0xFF), g: Int((n >> 8) & 0xFF), b: Int(n & 0xFF))
}
/// Linearizes a single sRGB channel (0-255) per WCAG 2.2.
///
/// 8-bit color values are gamma-encoded for display; WCAG luminance is
/// computed in linear-light space using the inverse sRGB transfer function.
/// The small-value branch is the linear segment of that curve.
func channel(_ c: Int) -> Double {
let v = Double(c) / 255.0
return v <= 0.03928 ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4)
}
/// WCAG relative luminance of a hex color on a 0..1 scale, or `nil` if the
/// hex string is invalid.
func luminance(_ color: String) -> Double? {
guard let rgb = hexToRgb(color) else { return nil }
// Rec. 709 luma coefficients for the red/green/blue primaries.
return 0.2126 * channel(rgb.r) + 0.7152 * channel(rgb.g) + 0.0722 * channel(rgb.b)
}
/// WCAG contrast ratio between two hex colors on a 1..21 scale, or `nil` if
/// either color is invalid.
///
/// The 0.05 offset models the ambient luminance assumed by WCAG, keeping the
/// ratio finite (and >= 1) even for identical colors.
func contrastRatio(_ fg: String, _ bg: String) -> Double? {
guard let l1 = luminance(fg), let l2 = luminance(bg) else { return nil }
return (max(l1, l2) + 0.05) / (min(l1, l2) + 0.05)
}
// Demo — run: swift swift.swift
func report(_ fg: String, _ bg: String) {
let body = contrastRatio(fg, bg).map { String(format: "%.2f", $0) } ?? "invalid"
print("contrast \(fg) on \(bg) = \(body)")
}
report("#000000", "#ffffff") // 21.00
report("#767676", "#ffffff") // 4.54
report("nope", "#ffffff") // invalid
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →