UNPKG

css-extras

Version:

Useful CSS custom functions using the new @function rule

644 lines (523 loc) 20.7 kB
/** CSS Extras - A collection of useful CSS custom functions @author Sindre Sorhus @license (MIT OR CC0-1.0) */ /* =================================== Math & Number Functions =================================== */ /** Negates a value (returns the negative). @param {Number} --value - The value to negate. @returns {Number} The negated value. @example padding: --negate(1em); */ @function --negate(--value) { result: calc(-1 * var(--value)); } /** Linear interpolation between two values. @param {Number} --from - Start value. @param {Number} --to - End value. @param {Number} --progress - Progress between 0 and 1. @returns {Number} Interpolated value. @example width: --lerp(100px, 200px, 0.5); */ @function --lerp(--from, --to, --progress) { result: calc(var(--from) + (var(--to) - var(--from)) * var(--progress)); } /** Maps a value from one range to another. @param {Number} --value - Input value. @param {Number} --in-min - Input range minimum. @param {Number} --in-max - Input range maximum. @param {Number} --out-min - Output range minimum. @param {Number} --out-max - Output range maximum. @returns {Number} Mapped value. @example font-size: --map-range(50vw, 320px, 1920px, 14px, 24px); */ @function --map-range(--value, --in-min, --in-max, --out-min, --out-max) { --progress: clamp(0, calc((var(--value) - var(--in-min)) / (var(--in-max) - var(--in-min))), 1); result: calc(var(--out-min) + (var(--out-max) - var(--out-min)) * var(--progress)); } /** Returns the ratio of two values. Supports values with different units, unlike regular division. @param {CalcSum} --value - Input value. @param {CalcSum} --to-value - Another input value. @returns {Number} The ratio between two values. @example scale: --ratio(16px, 1em); */ @function --ratio(--value, --to-value) { result: tan(atan2(var(--value), var(--to-value))); } /* =================================== Color Functions =================================== */ /** Returns a semi-transparent version of any color. @param {Color} --color - The base color. @param {Number} --opacity - Opacity value (0-100% or 0-1). @returns {Color} Color with opacity. @example background: --opacity(blue, 50%); */ @function --opacity(--color, --opacity) { result: rgb(from var(--color) r g b / var(--opacity)); } /** Lightens a color by mixing with white. Uses OKLab color space for perceptually uniform mixing. @param {Color} --color - The base color. @param {Number} --amount - Amount to lighten (0-100%). @returns {Color} Lightened color. @example background: --tint(blue, 20%); */ @function --tint(--color, --amount: 10%) { result: color-mix(in oklab, var(--color), white var(--amount)); } /** Darkens a color by mixing with black. Uses OKLab color space for perceptually uniform mixing. @param {Color} --color - The base color. @param {Number} --amount - Amount to darken (0-100%). @returns {Color} Darkened color. @example background: --shade(blue, 20%); */ @function --shade(--color, --amount: 10%) { result: color-mix(in oklab, var(--color), black var(--amount)); } /** Adjusts color saturation. Uses OKLCH color space for perceptually uniform chroma adjustment. Chroma is clamped to 0.4 for safe display. @param {Color} --color - The base color. @param {Number} --amount - Chroma multiplier. @returns {Color} Adjusted color. @example color: --saturate(red, 1.5); */ @function --saturate(--color, --amount: 1.2) { result: oklch(from var(--color) l clamp(0, calc(c * var(--amount)), 0.4) h); } /** Adjusts color lightness. Uses OKLCH color space for perceptually uniform lightness adjustment. Maintains chroma independently. @param {Color} --color - The base color. @param {Number} --amount - Lightness adjustment (-100% to 100%). @returns {Color} Adjusted color. @example background: --lighten(blue, 20%); */ @function --lighten(--color, --amount: 10%) { result: oklch(from var(--color) clamp(0, calc(l + var(--amount) / 100%), 1) c h); } /** Darkens a color by reducing lightness. Uses OKLCH color space for perceptually uniform lightness adjustment. Unlike `--shade()` which mixes with black, this directly reduces the lightness value. @param {Color} --color - The base color. @param {Number} --amount - Lightness reduction (0-100%). @returns {Color} Darkened color. @example background: --darken(blue, 20%); */ @function --darken(--color, --amount: 10%) { result: oklch(from var(--color) clamp(0, calc(l - var(--amount) / 100%), 1) c h); } /** Rotates the hue of a color. Uses OKLCH color space for perceptually uniform hue rotation. @param {Color} --color - The base color. @param {Angle} --degrees - Degrees to rotate hue. @returns {Color} Color with rotated hue. @example background: --rotate-hue(blue, 180deg); */ @function --rotate-hue(--color, --degrees: 30deg) { result: oklch(from var(--color) l c calc(h + var(--degrees))); } /** Returns the complementary color. Uses OKLCH color space for perceptually accurate complementary colors. @param {Color} --color - The base color. @returns {Color} Complementary color. @example border-color: --complement(blue); */ @function --complement(--color) { result: oklch(from var(--color) l c calc(h + 180deg)); } /** Inverts a color. @param {Color} --color - The color to invert. @returns {Color} Inverted color. @example background: --invert(white); */ @function --invert(--color) { result: rgb(from var(--color) calc(255 - r) calc(255 - g) calc(255 - b) / alpha); } /** Converts a color to grayscale. Uses OKLCH color space by setting chroma to 0. @param {Color} --color - The color to convert. @returns {Color} Grayscale color. @example filter: --grayscale(var(--brand-color)); */ @function --grayscale(--color) { result: oklch(from var(--color) l 0 h); } /* Uses HWB color space trick: converts to grayscale in OKLCH, then uses HWB's blackness/whiteness values amplified by a large multiplier to create a binary black/white decision based on brightness. This provides excellent contrast decisions without needing numeric luminance extraction. */ /** Returns black or white text color for optimal contrast on a background. @param {Color} --bg - Background color. @returns {Color} Black or white for optimal readability. @example color: --text-on(var(--bg-color)); */ @function --text-on(--bg) { result: hwb(from oklch(from var(--bg) l 0 0) h calc((b - 50) * 999) calc((w - 50) * 999)); } /** Removes transparency from a color, making it fully opaque. @param {Color} --color - Color with alpha channel. @returns {Color} Fully opaque version of the color. @example background: --opaque(var(--semi-transparent-bg)); */ @function --opaque(--color) { result: rgb(from var(--color) r g b / 1); } /** Mixes two colors in OKLab color space. Uses perceptually uniform OKLab color space for natural-looking color mixing. @param {Color} --color1 - First color. @param {Color} --color2 - Second color. @param {Number} --amount - Amount of second color to mix (0-100%). @returns {Color} Mixed color. @example background: --mix(red, blue, 30%); */ @function --mix(--color1, --color2, --amount: 50%) { result: color-mix(in oklab, var(--color1), var(--color2) var(--amount)); } /** Returns a triadic color harmony. Triadic colors are evenly spaced around the color wheel (120° apart). @param {Color} --color - Base color. @param {Number} --index - Which triadic color (1 or 2). @returns {Color} Triadic color. @example color: --triadic(blue, 1); */ @function --triadic(--color, --index: 1) { result: oklch(from var(--color) l c calc(h + 120deg * var(--index))); } /** Returns a tetradic (square) color harmony. Tetradic colors are evenly spaced around the color wheel (90° apart). @param {Color} --color - Base color. @param {Number} --index - Which tetradic color (1, 2, or 3). @returns {Color} Tetradic color. @example color: --tetradic(blue, 2); */ @function --tetradic(--color, --index: 1) { result: oklch(from var(--color) l c calc(h + 90deg * var(--index))); } /** Creates a semi-transparent black. @param {Number} --opacity - Opacity value (0-100% or 0-1). @returns {Color} Semi-transparent black. @example box-shadow: 0 2px 4px --black(20%); */ @function --black(--opacity: 50%) { result: rgb(0 0 0 / var(--opacity)); } /** Creates a semi-transparent white. @param {Number} --opacity - Opacity value (0-100% or 0-1). @returns {Color} Semi-transparent white. @example background: --white(90%); */ @function --white(--opacity: 50%) { result: rgb(255 255 255 / var(--opacity)); } /* =================================== Typography Functions =================================== */ /** Creates fluid typography that scales with viewport. NOTE: This function is mathematically equivalent to `--responsive-value()` but optimized for typography. Use this for `font-size`, `--responsive-value()` for other properties. @param {Length} --min - Minimum font size. @param {Length} --max - Maximum font size. @param {Length} --min-viewport - Minimum viewport width. @param {Length} --max-viewport - Maximum viewport width. @returns {Length} Fluid font size. @example font-size: --fluid-type(16px, 24px, 320px, 1280px); */ @function --fluid-type(--min, --max, --min-viewport: 320px, --max-viewport: 1280px) { --slope: calc((var(--max) - var(--min)) / (var(--max-viewport) - var(--min-viewport))); --intercept: calc(var(--min) - var(--slope) * var(--min-viewport)); result: clamp(var(--min), calc(var(--intercept) + var(--slope) * 100vw), var(--max)); } /** Creates a modular scale value. @param {Number} --base - Base size. @param {Number} --ratio - Scale ratio. @param {Number} --step - Step in the scale. @returns {Length} Scaled value. @example font-size: --modular-scale(1rem, 1.25, 3); */ @function --modular-scale(--base: 1rem, --ratio: 1.25, --step: 0) { result: calc(var(--base) * pow(var(--ratio), var(--step))); } /** Calculates line height as a length value based on font size. Returns a length (e.g., 24px) rather than a unitless ratio. Use this when you need an absolute line height value. @param {Length} --font-size - The font size. @param {Number} --multiplier - Line height multiplier. @returns {Length} Line height as a length. @example line-height: --line-height-length(16px, 1.6); */ @function --line-height-length(--font-size, --multiplier: 1.5) { result: calc(var(--font-size) * var(--multiplier)); } /** Calculates line height as a unitless ratio. Returns a number (e.g., 1.5) which is recommended for better inheritance in CSS. @param {Length} --line-height - The desired line height as a length. @param {Length} --font-size - The font size. @returns {Number} Unitless line height ratio. @example line-height: --line-height-ratio(24px, 16px); */ @function --line-height-ratio(--line-height, --font-size) { result: calc(var(--line-height) / var(--font-size)); } /** Creates unitless line height from font size (recommended for better inheritance). NOTE: Only works correctly with pixel font sizes. For rem/em values, use `--line-height-length()` or `--line-height-ratio()` instead. @param {Length} --font-size - Font size in pixels. @param {Number} --multiplier - Line height multiplier. @returns {Number} Unitless line height. @example line-height: --line-height-unitless(16px, 1.5); */ @function --line-height-unitless(--font-size: 16px, --multiplier: 1.5) { result: calc(var(--font-size) * var(--multiplier) / 1px); } /* =================================== Layout Functions =================================== */ /** Creates responsive sidebar layout columns. @param {Length} --sidebar-width - Width of sidebar. @param {Length} --content-min - Minimum width of content area. @returns {Length} Grid template columns value. @example grid-template-columns: --sidebar-layout(250px, 20ch); */ @function --sidebar-layout(--sidebar-width: 20ch, --content-min: 20ch) { result: minmax(var(--sidebar-width), 1fr) minmax(var(--content-min), 3fr); } /** Conditional border radius that removes at viewport edges. @param {Length} --radius - Border radius value. @param {Length} --edge-dist - Distance from viewport edge. @returns {Length} Computed border radius. @example border-radius: --conditional-radius(1rem, 8px); */ @function --conditional-radius(--radius, --edge-dist: 4px) { /* Multiply by large number to amplify small differences, creating binary 0/radius effect */ result: clamp(0px, ((100vw - var(--edge-dist)) - 100%) * 1e5, var(--radius)); } /** Creates a responsive value that scales between two sizes. NOTE: This function is mathematically equivalent to `--fluid-type()` but uses a simpler lerp-based approach. Use this for spacing/sizing, `--fluid-type()` for typography. @param {Length} --small - Minimum value. @param {Length} --large - Maximum value. @param {Length} --viewport-min - Minimum viewport width. @param {Length} --viewport-max - Maximum viewport width. @returns {Length} Responsive value. @example padding: --responsive-value(1rem, 2rem, 320px, 1200px); */ @function --responsive-value(--small, --large, --viewport-min: 320px, --viewport-max: 1200px) { --progress: calc((100vw - var(--viewport-min)) / (var(--viewport-max) - var(--viewport-min))); --clamped-progress: clamp(0, var(--progress), 1); result: calc(var(--small) + (var(--large) - var(--small)) * var(--clamped-progress)); } /** Calculates height from aspect ratio and maximum constraints. @param {Number} --ratio - Aspect ratio (e.g., 16/9). @param {Length} --max-width - Maximum width. @param {Length} --max-height - Maximum height. @returns {Length} Computed height. @example height: --aspect-height(16/9, 100vw, 100vh); */ @function --aspect-height(--ratio: 1, --max-width: 100%, --max-height: 100%) { --computed-height: calc(var(--max-width) / var(--ratio)); result: min(var(--computed-height), var(--max-height)); } /** Calculates width from aspect ratio and maximum constraints. @param {Number} --ratio - Aspect ratio (e.g., 16/9). @param {Length} --max-height - Maximum height. @param {Length} --max-width - Maximum width. @returns {Length} Computed width. @example width: --aspect-width(16/9, 100vh, 100vw); */ @function --aspect-width(--ratio: 1, --max-height: 100%, --max-width: 100%) { --computed-width: calc(var(--max-height) * var(--ratio)); result: min(var(--computed-width), var(--max-width)); } /* =================================== Spacing Functions =================================== */ /** Creates consistent spacing based on a scale. Recommended range: 0-10. Higher values create exponentially larger spacing. @param {Number} --level - Spacing level (0-10). @param {Length} --base - Base spacing unit. @returns {Length} Computed spacing. @example margin: --spacing(3); */ @function --spacing(--level: 1, --base: 0.25rem) { result: calc(var(--base) * pow(2, var(--level))); } /** Creates inset spacing for containers. @param {Length} --padding - Base padding. @param {Length} --max-width - Maximum container width. @returns {Length} Responsive padding. @example padding: --container-padding(2rem, 1200px); */ @function --container-padding(--padding: 1rem, --max-width: 1200px) { --available: calc(100vw - var(--max-width)); --side-space: max(var(--padding), calc(var(--available) / 2)); result: var(--side-space); } /* =================================== Animation Functions =================================== */ /** Creates a simple easing curve value. @param {Number} --progress - Animation progress (0-1). @returns {Number} Eased value. @example transform: translateY(--ease-out(var(--progress))); */ @function --ease-out(--progress) { --inverse: calc(1 - var(--progress)); result: calc(1 - var(--inverse) * var(--inverse)); } /** Creates elastic easing. @param {Number} --progress - Animation progress (0-1). @param {Number} --amplitude - Amplitude of elasticity. @returns {Number} Eased value. @example transform: scale(--elastic-ease(var(--progress), 1.2)); */ @function --elastic-ease(--progress, --amplitude: 1) { --p: calc(var(--progress) * 3.14159); result: calc(var(--amplitude) * sin(var(--p) * 10) * exp(calc(-5 * var(--progress)))); } /* =================================== Utility Functions =================================== */ /** Converts pixels to rem. @param {Length} --pixels - Pixel value. @param {Length} --base - Base font size. @returns {Length} Rem value. @example font-size: --px-to-rem(24px); */ @function --px-to-rem(--pixels, --base: 16px) { result: calc(var(--pixels) / var(--base) * 1rem); } /** Converts rem to pixels. @param {Length} --rems - Rem value. @param {Length} --base - Base font size. @returns {Length} Pixel value. @example width: --rem-to-px(2rem); */ @function --rem-to-px(--rems, --base: 16px) { result: calc(var(--rems) / 1rem * var(--base)); } /* =================================== Grid Functions =================================== */ /** Creates responsive grid columns. @param {Number} --min-width - Minimum column width. @param {Number} --max-cols - Maximum number of columns. @returns {Grid} Grid template columns value. @example grid-template-columns: --auto-grid(250px, 4); */ @function --auto-grid(--min-width: 250px, --max-cols: 4) { result: repeat( auto-fit, minmax(max(var(--min-width), calc(100% / var(--max-cols))), 1fr) ); } /** Creates a CSS grid span value. Ensures the span is an integer value. @param {Number} --columns - Number of columns to span. @param {Number} --total - Total columns in grid. @returns {Span} Grid column span (rounded to integer). @example grid-column: --grid-span(3, 12); */ @function --grid-span(--columns: 1, --total: 12) { result: span round(clamp(1, var(--columns), var(--total))); } /* =================================== Filter Functions =================================== */ /** Creates a smooth shadow. Generates three shadow layers. The spread-factor controls how distributed the shadows are. @param {Color} --color - Shadow color. @param {Length} --size - Shadow size. @param {Number} --spread-factor - Controls shadow distribution (higher = tighter shadows). @returns {Shadow} Layered box shadow. @example box-shadow: --smooth-shadow(black, 20px, 3); */ @function --smooth-shadow(--color: rgb(0 0 0 / 0.2), --size: 12px, --spread-factor: 3) { --step: calc(var(--size) / var(--spread-factor)); result: 0 var(--step) calc(var(--step) * 2) rgb(from var(--color) r g b / 0.25), 0 calc(var(--step) * 2) calc(var(--step) * 3) rgb(from var(--color) r g b / 0.18), 0 calc(var(--step) * 3) calc(var(--step) * 4) rgb(from var(--color) r g b / 0.12); } /** Creates a glow effect. @param {Color} --color - Glow color. @param {Length} --size - Glow size. @param {Number} --intensity - Glow intensity (0-1). @returns {Shadow} Glow shadow. @example box-shadow: --glow(cyan, 10px, 0.5); */ @function --glow(--color: white, --size: 10px, --intensity: 0.5) { result: 0 0 var(--size) rgb(from var(--color) r g b / var(--intensity)); } /* =================================== Theme Functions =================================== */ /** Theme-aware value switcher for light/dark mode. Uses CSS `if()` with color-scheme query. Requires `color-scheme: light dark` on `:root`. Works with ANY value type (colors, lengths, etc.), not just colors. > [!NOTE] > CSS has a native [`light-dark()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/light-dark) function for colors. The custom `--light-dark()` function is more powerful as it works with any value type, not just colors. @param {Any} --light - Value for light mode. @param {Any} --dark - Value for dark mode. @returns {Any} Theme-appropriate value. @example color: --light-dark(black, white); @example background-image: --light-dark(url(light.svg), url(dark.svg)); @example padding: --light-dark(0.75rem, 1rem); */ @function --light-dark(--light, --dark) { result: if(color-scheme(dark): var(--dark); else: var(--light)); } /** Creates a theme-aware color with automatic adjustment. Uses CSS `if()` with color-scheme query. Requires `color-scheme: light dark` on `:root`. In light mode, mixes the base color with white (default 85% white). In dark mode, mixes the base color with black (default 15% black). @param {Color} --base - Base color. @param {Number} --light-mix - Percentage of white to mix in light mode. @param {Number} --dark-mix - Percentage of black to mix in dark mode. @returns {Color} Theme-adjusted color. @example background: --theme-color(blue, 80%, 20%); */ @function --theme-color(--base, --light-mix: 85%, --dark-mix: 15%) { --light-result: color-mix(in oklab, white var(--light-mix), var(--base)); --dark-result: color-mix(in oklab, black var(--dark-mix), var(--base)); result: if(color-scheme(dark): var(--dark-result); else: var(--light-result)); }