HSV is how most color pickers think. Photoshop calls it HSB, Figma lists it beside HEX, and OpenCV uses it to pick objects out of camera frames. Browsers never read it. CSS has no hsv() function, so a color chosen in HSV has to become HEX, rgb() or hsl() before a stylesheet accepts it.
Same three numbers, four different meanings
Most bad conversions here come from a scale mismatch, not from the formula. Each program stores HSV in its own range, and the numbers look plausible in every one of them.
| Where the numbers came from | Hue | Saturation, value | Pick this scale |
|---|---|---|---|
| Photoshop, Figma, Affinity (labelled HSB) | 0 to 360 | 0 to 100 | Degrees + percent |
| GIMP, Krita | 0 to 360 | 0 to 100 | Degrees + percent |
| OpenCV on 8-bit images | 0 to 179 | 0 to 255 | OpenCV 8-bit |
Python colorsys, Unity, Blender | 0 to 1 | 0 to 1 | Unit floats |
Android Color.HSVToColor | 0 to 360 | 0 to 1 | Android |
A real case: OpenCV reports a pixel as (100, 180, 220). Read as degrees, hue 100 lands on a bright green, and 180 overflows the percent range. OpenCV halves hue to fit one byte, so the true hue is 200 degrees, a clear sky blue, #41A8DC. Pick the OpenCV 8-bit scale and the converter doubles the hue and rescales S and V for you.
The conversion, worked by hand
Here is the default color on this page, hsv(204, 72%, 86%), taken through the same steps the math panel shows live:
C = V x S = 0.86 x 0.72 = 0.6192
H' = H / 60 = 204 / 60 = 3.4 (sector 3)X = C x (1 - |H' mod 2 - 1|) = 0.6192 x 0.6 = 0.3715
m = V - C = 0.86 - 0.6192 = 0.2408
sector 3 gives (R', G', B') = (0, X, C)R = (0 + 0.2408) x 255 = 61.4 -> 61 -> 3D
G = (0.3715 + 0.2408) x 255 = 156.1 -> 156 -> 9C
B = (0.6192 + 0.2408) x 255 = 219.3 -> 219 -> DB
Result: #3D9CDBTwo details trip up hand calculations. First, hue falls into one of six 60-degree sectors, and the sector decides which channel gets C, which gets X, and which gets zero:
- 0° to 60°
(C, X, 0) - 60° to 120°
(X, C, 0) - 120° to 180°
(0, C, X) - 180° to 240°
(0, X, C) - 240° to 300°
(X, 0, C) - 300° to 360°
(C, 0, X)
Second, round once, at the very end. Round C, X or m to two places along the way and a channel slips by one step. hsv(204, 55%, 60%) should give #457799, but early rounding returns #457899, and nothing on screen tells you why.
Why the copy row offers hwb()
If you want to keep thinking in HSV inside a stylesheet, hwb() is the closest native option. It shares the hue wheel and swaps saturation and value for whiteness and blackness:
W = (1 - S) x V
B = 1 - VSafari has supported hwb() since version 15, Firefox since 96 and Chrome since 101. Older CSS minifiers, some email clients and design tokens pipelines still reject it, so HEX stays the safer default for anything shared outside a modern browser.
Where the HEX result stops being exact
- Whole numbers skip colors. Integer HSV gives 361 × 101 × 101, about 3.7 million inputs, against 16.7 million HEX codes. Plenty of HEX values have no exact whole-number HSV twin, so a HEX to HSV to HEX round trip shifts a channel by one now and then. Type decimals when a round trip has to land on the same code.
- Hue stops mattering at the edges. At saturation 0 every hue returns the same grey. At value 0 every input returns #000000. Converting those back reports hue 0, which looks like a bug but is the standard convention.
- Value is not perceived lightness. Pure yellow and pure blue both sit at V 100, yet the yellow looks far lighter. Check text colors in the contrast checker instead of trusting V, and reach for OKLCH when a palette needs even lightness across hues.
- sRGB only, no alpha. Output is a six-digit sRGB code. Display P3 colors and eight-digit
#RRGGBBAAtransparency fall outside this tool. For alpha, run the result through the HEX to RGBA converter.
Check the scale, read the math panel when a number looks off, and trust the HEX for anything going into CSS.
