Chain Lighten, Darken, Alpha, and Mix on a Single Color Helper — No Runtime Cost in React
Color Helper for React Styles
color() is a chainable build-time helper for transforming colors — lighten, darken, adjust alpha, mix, rotate hue, and more. Everything runs at build time: the result lands in the generated CSS as a static string, with no runtime cost.
Implementation Note
The Salty CSS color function is built on top of the color library by Qix, providing a robust and well-tested foundation for color manipulation.
When transformations happen
color() runs at build time. The transformed value (e.g. rgba(0, 0, 0, 0.5)) lands in the generated CSS as a static string — there is no runtime cost and no JS bundle bloat at render time. The trade-off: you can only transform values that are knowable at build time, which includes raw colors and static token references like {colors.brand.primary}. Values that change at runtime (responsive or conditional tokens, plus anything computed via {props.X}) are passed through unchanged because there's no way to derive a shade from a value that doesn't exist yet.
For runtime-derived shades on themed tokens, declare the shade variants directly under conditional variables instead.
Color spaces and inputs
color() parses sRGB by default — the same color space the browser uses for #rrggbb, rgb(), hsl(), and named CSS colors. Transformations are computed in HSL space internally, which is why .lighten() / .darken() / .saturate() operate on perceptual axes rather than raw channels. The output is always returned as a CSS-compatible string in the format you asked for (.hex(), .rgb(), etc.) — default is rgb() / rgba().
Wide-gamut color (P3, oklch, etc.) is not part of the input parser today; if you need it, ship the wide-gamut value as a raw string and gate it behind @supports (color(display-p3 1 1 1)).
Errors and invalid input
If color() can't parse the input (e.g. you reference a token path that doesn't exist, or pass a malformed string), the call throws at build time. With defineConfig({ strict: 'warn' }) you'll get a warning instead and the original value passes through. Either way, the failure surfaces in your terminal — it doesn't reach the browser as silent fallback.
Basic Usage
The color function provides a chainable API for color manipulation:
// /components/my-component.css.ts import { styled } from "@salty-css/react/styled"; import { color } from "@salty-css/core/helpers"; export const Card = styled("div", { base: { // Create semi-transparent black background backgroundColor: color("#000000").alpha(0.5), // Create a lighter version of a color borderColor: color("#0070f3").lighten(0.2), // Create a darker text color color: color("#ffffff").darken(0.1), }, });
Color Sources
The color function accepts various input formats:
// Hex color strings color("#ff0000"); color("#f00"); // RGB/RGBA strings color("rgb(255, 0, 0)"); color("rgba(255, 0, 0, 0.5)"); // HSL/HSLA strings — useful when you want predictable lighten/darken behaviour. color("hsl(0, 100%, 50%)"); color("hsla(0, 100%, 50%, 0.5)"); color("hsl(210, 60%, 45%)").darken(0.1); // → hsl(210, 60%, ~40%) // Named CSS colors color("red"); color("steelblue"); // Static CSS variables (responsive or conditional variables are not supported) color("{colors.brand.primary}"); color("{theme.accentColor}");
Available Methods
The color utility offers numerous chainable methods:
Transparency
// Set specific alpha/opacity value (0-1) color("#ff0000").alpha(0.5); // 50% opacity red // Fade by a relative amount color("#ff0000").fade(0.2); // Reduce opacity by 20% // Make fully opaque color("rgba(255, 0, 0, 0.5)").opaque(); // Remove transparency
Lightness Adjustments
// Lighten by a relative amount (0-1) color("#ff0000").lighten(0.2); // 20% lighter red // Darken by a relative amount (0-1) color("#ff0000").darken(0.2); // 20% darker red // Set absolute lightness (0-1) color("#ff0000").lightness(0.8); // Set to 80% lightness
Saturation Adjustments
// Increase saturation by a relative amount (0-1) color("#ff0000").saturate(0.2); // 20% more saturated // Decrease saturation by a relative amount (0-1) color("#ff0000").desaturate(0.2); // 20% less saturated // Remove all saturation (create grayscale) color("#ff0000").grayscale(); // Convert to grayscale
Hue Adjustments
// Rotate hue by a specific amount in degrees color("#ff0000").rotate(90); // Rotate hue by 90 degrees // Set absolute hue (0-360) color("#ff0000").hue(180); // Set hue to 180 degrees
Color Blending
// Mix with another color (second parameter is mix percentage, 0-1) color("#ff0000").mix("#0000ff", 0.5); // 50% mix of red and blue // Get complementary color (opposite on color wheel) color("#ff0000").negate(); // Complementary color
Format Conversion
You can output colors in different formats:
// Get color as hex color("rgb(255, 0, 0)").hex(); // Returns "#ff0000" // Get color as RGB string color("#ff0000").rgb(); // Returns "rgb(255, 0, 0)" // Get color as RGBA string color("#ff0000").alpha(0.5).rgba(); // Returns "rgba(255, 0, 0, 0.5)" // Get color as HSL string color("#ff0000").hsl(); // Returns "hsl(0, 100%, 50%)" // Get color as HSLA string color("#ff0000").alpha(0.5).hsla(); // Returns "hsla(0, 100%, 50%, 0.5)"
Advanced Examples
Creating a Color Palette
import { styled } from "@salty-css/react/styled"; import { color } from "@salty-css/core/helpers"; // Define a single brand color and derive a palette const brandColor = "#0070f3"; export const ColorPalette = styled("div", { base: { // Main brand color "--color-brand-main": brandColor, // Lighter variants "--color-brand-light": color(brandColor).lighten(0.2), "--color-brand-lighter": color(brandColor).lighten(0.4), // Darker variants "--color-brand-dark": color(brandColor).darken(0.2), "--color-brand-darker": color(brandColor).darken(0.4), // Desaturated variants "--color-brand-muted": color(brandColor).desaturate(0.3), // Transparent variants "--color-brand-transparent": color(brandColor).alpha(0.2), }, });
Interactive Element States
import { styled } from "@salty-css/react/styled"; import { color } from "@salty-css/core/helpers"; export const Button = styled("button", { base: { backgroundColor: "{colors.brand.primary}", color: "#ffffff", transition: "background-color 0.2s ease", "&:hover": { // Lighten on hover backgroundColor: color("{colors.brand.primary}").lighten(0.1), }, "&:active": { // Darken on press backgroundColor: color("{colors.brand.primary}").darken(0.1), }, "&:disabled": { // Desaturate and reduce opacity when disabled backgroundColor: color("{colors.brand.primary}") .desaturate(0.5) .alpha(0.6), }, }, });