Type-safe CSS calc() expression builder with chainable operations for dynamic styling and responsive calculations.
Create CSS calc() expressions with a fluent, chainable API.
/**
* Create a chainable CSS calc() expression
* @param x - Initial operand (number, string, or CSSVar)
* @returns CalcChain for chaining operations
*/
function calc(x: Operand): CalcChain;
type Operand = string | number | CSSVar;
interface CalcChain {
/** Add operands to the expression */
add: (...operands: Operand[]) => CalcChain;
/** Subtract operands from the expression */
subtract: (...operands: Operand[]) => CalcChain;
/** Multiply the expression by operands */
multiply: (...operands: Operand[]) => CalcChain;
/** Divide the expression by operands */
divide: (...operands: Operand[]) => CalcChain;
/** Negate the expression */
negate: () => CalcChain;
/** Convert to CSS calc() string */
toString: () => string;
}
interface CSSVar {
variable: string;
reference: string;
}Usage Examples:
import { calc } from "@chakra-ui/theme-tools/css-calc";
import { cssVar } from "@chakra-ui/theme-tools/css-var";
// Basic calculations
const simpleCalc = calc("100%").subtract("20px").toString();
// Returns: "calc(100% - 20px)"
const complexCalc = calc("50vw")
.subtract("2rem")
.multiply(2)
.add("10px")
.toString();
// Returns: "calc((50vw - 2rem) * 2 + 10px)"
// With CSS variables
const containerWidth = cssVar("container-width");
const spacing = cssVar("spacing");
const dynamicWidth = calc(containerWidth)
.subtract(spacing)
.multiply(0.5)
.toString();
// Returns: "calc((var(--container-width) - var(--spacing)) * 0.5)"
// Negate values
const negativeMargin = calc("1rem").negate().toString();
// Returns: "calc(-1rem)"Use static methods for direct calculations without chaining.
/**
* Static methods available on calc function
*/
const calc: {
/** Add multiple operands */
add: (...operands: Operand[]) => string;
/** Subtract operands (first - rest) */
subtract: (...operands: Operand[]) => string;
/** Multiply operands */
multiply: (...operands: Operand[]) => string;
/** Divide operands (first / rest) */
divide: (...operands: Operand[]) => string;
/** Negate an operand */
negate: (operand: Operand) => string;
};Usage Examples:
import { calc } from "@chakra-ui/theme-tools/css-calc";
// Static method usage
const addition = calc.add("50%", "20px", "1rem");
// Returns: "calc(50% + 20px + 1rem)"
const subtraction = calc.subtract("100vh", "60px", "2rem");
// Returns: "calc(100vh - 60px - 2rem)"
const multiplication = calc.multiply("100%", 0.8);
// Returns: "calc(100% * 0.8)"
const division = calc.divide("100vw", 3);
// Returns: "calc(100vw / 3)"
const negation = calc.negate("10px");
// Returns: "-10px" (or "calc(10px * -1)" for complex values)import { calc } from "@chakra-ui/theme-tools/css-calc";
// Container with responsive padding
const containerStyles = {
width: calc("100vw").subtract("2rem").toString(), // Mobile padding
maxWidth: "1200px",
margin: "0 auto",
// Responsive grid columns
gridTemplateColumns: `repeat(auto-fit, minmax(${calc("300px").toString()}, 1fr))`,
// Dynamic spacing based on viewport
gap: calc("2vw").add("1rem").toString()
};
// Sidebar layout calculations
const sidebarWidth = "250px";
const headerHeight = "60px";
const layoutStyles = {
sidebar: {
width: sidebarWidth,
height: calc("100vh").subtract(headerHeight).toString()
},
main: {
width: calc("100%").subtract(sidebarWidth).toString(),
height: calc("100vh").subtract(headerHeight).toString(),
marginLeft: sidebarWidth
}
};import { calc } from "@chakra-ui/theme-tools/css-calc";
import { cssVar } from "@chakra-ui/theme-tools/css-var";
// Theme variables
const spacing = {
xs: cssVar("spacing-xs", { fallback: "0.25rem" }),
sm: cssVar("spacing-sm", { fallback: "0.5rem" }),
md: cssVar("spacing-md", { fallback: "1rem" }),
lg: cssVar("spacing-lg", { fallback: "2rem" })
};
// Card component with calculated dimensions
const cardStyles = {
// Dynamic padding based on size variant
small: {
padding: calc(spacing.sm).multiply(2).toString(),
margin: spacing.sm.reference
},
medium: {
padding: calc(spacing.md).multiply(1.5).toString(),
margin: spacing.md.reference
},
large: {
padding: calc(spacing.lg).add(spacing.sm).toString(),
margin: calc(spacing.lg).divide(2).toString()
}
};
// Form layout calculations
const formSpacing = cssVar("form-spacing", { fallback: "1rem" });
const inputHeight = cssVar("input-height", { fallback: "2.5rem" });
const formStyles = {
fieldGroup: {
marginBottom: calc(formSpacing).multiply(1.5).toString()
},
label: {
marginBottom: calc(formSpacing).divide(2).toString()
},
input: {
height: inputHeight.reference,
padding: calc(formSpacing).divide(2).toString()
},
textarea: {
minHeight: calc(inputHeight).multiply(3).toString(),
padding: calc(formSpacing).divide(2).toString()
}
};import { calc } from "@chakra-ui/theme-tools/css-calc";
// Staggered animation delays
const createStaggeredDelay = (index: number, baseDelay = 100) =>
calc(baseDelay).multiply(index).add("ms").toString();
// Modal animations with calculated transforms
const modalAnimations = {
enter: {
transform: `translateY(${calc("-50px").toString()}) scale(0.9)`,
opacity: 0
},
enterActive: {
transform: "translateY(0) scale(1)",
opacity: 1,
transition: "all 300ms ease-out"
},
exit: {
transform: `translateY(${calc("50px").toString()}) scale(0.9)`,
opacity: 0,
transition: "all 200ms ease-in"
}
};
// Progress bar calculations
const progressBarStyles = (progress: number) => ({
width: calc(progress).multiply("1%").toString(),
transform: `translateX(${calc("-100%").add(calc(progress).multiply("1%")).toString()})`
});import { calc } from "@chakra-ui/theme-tools/css-calc";
// CSS Grid with calculated gaps
const gridGap = "1rem";
const gridColumns = 3;
const gridStyles = {
display: "grid",
gridTemplateColumns: `repeat(${gridColumns}, ${calc("100%").divide(gridColumns).subtract(calc(gridGap).multiply(gridColumns - 1).divide(gridColumns)).toString()})`,
gap: gridGap
};
// Flexbox with calculated flex-basis
const createFlexItem = (ratio: number, gap: string = "1rem") => ({
flexBasis: calc(`${ratio * 100}%`).subtract(gap).toString(),
marginRight: gap
});
const flexLayoutStyles = {
container: {
display: "flex",
gap: "1rem"
},
sidebar: createFlexItem(0.25), // 25% width minus gap
main: createFlexItem(0.75) // 75% width minus gap
};