Utils Reference


/**
 * Baseline 14px, Major Second Scale font size (1.125x), font size snaps to 1px, line height snaps to 4px.
 */
const fontScales: Record<string, [fontSize: number, lineHeight: number]> = {
  "-3": [10, 16],
  "-2": [11, 16],
  "-1": [12, 16],
  0: [14, 20],
  1: [16, 24],
  2: [18, 28],
  3: [20, 28],
  4: [22, 28],
  5: [25, 32],
  6: [28, 36],
  7: [32, 40],
  8: [36, 44],
  9: [40, 48],
  10: [45, 52],
  11: [51, 60],
  12: [57, 64],
  13: [64, 72],
  14: [72, 80],
  15: [81, 88],
  16: [91, 96],
  17: [103, 104],
};

/**
 * Dynamic letter spacing algorithm, using recommended parameters for Inter.
 *
 * From https://rsms.me/inter/dynmetrics/
 */
function getLetterSpacing(fontSize: number) {
  let a = -0.0223;
  let b = 0.185;
  let c = -0.1745;
  let spacing = a + b * Math.exp(c * fontSize);
  return spacing;
}

// Basic math fns, taken from tailwindcss
const round = (num: number) =>
  num
    .toFixed(7)
    .replace(/(\.[0-9]+?)0+$/, "$1")
    .replace(/\.0$/, "");
export const rem = (px: number) => `${round(px / 16)}rem`;
export const em = (px: number, base: number) => `${round(px / base)}em`;

export const RECOMMENDED_UTILS = {
  //background
  bg: (background: CSS["background"]) => ({
    background,
  }),
  textBG: (bg: CSS["backgroundImage"]) =>
    bg
      ? {
          "-webkit-background-clip": "text",
          backgroundClip: "text",
          color: "transparent",
          backgroundImage: bg,
        }
      : {},

  // border
  borderT: (borderTop: CSS["borderTop"]) => ({
    borderTop,
  }),
  borderR: (borderRight: CSS["borderRight"]) => ({
    borderRight,
  }),
  borderB: (borderBottom: CSS["borderBottom"]) => ({
    borderBottom,
  }),
  borderL: (borderLeft: CSS["borderLeft"]) => ({
    borderLeft,
  }),
  borderX: (borderInline: CSS["borderLeft"]) => ({
    borderLeft: borderInline,
    borderRight: borderInline,
  }),
  borderY: (borderBlock: CSS["borderTop"]) => ({
    borderTop: borderBlock,
    borderRight: borderBlock,
  }),

  // border-radius
  r: (radius: CSS["borderRadius"]) => ({
    borderRadius: radius,
  }),
  rt: (radius: CSS["borderRadius"]) => ({
    borderTopLeftRadius: radius,
    borderTopRightRadius: radius,
  }),
  rr: (radius: CSS["borderRadius"]) => ({
    borderTopRightRadius: radius,
    borderBottomRightRadius: radius,
  }),
  rb: (radius: CSS["borderRadius"]) => ({
    borderBottomRightRadius: radius,
    borderBottomLeftRadius: radius,
  }),
  rl: (radius: CSS["borderRadius"]) => ({
    borderTopLeftRadius: radius,
    borderBottomLeftRadius: radius,
  }),
  rtl: (radius: CSS["borderRadius"]) => ({
    borderTopLeftRadius: radius,
  }),
  rtr: (radius: CSS["borderRadius"]) => ({
    borderTopRightRadius: radius,
  }),
  rbr: (radius: CSS["borderRadius"]) => ({
    borderBottomRightRadius: radius,
  }),
  rbl: (radius: CSS["borderRadius"]) => ({
    borderBottomLeftRadius: radius,
  }),

  // display
  d: (display: CSS["display"]) => ({ display }),

  // divide
  divideColor: (offset: CSS["borderColor"]) => ({
    "& > * + *": {
      borderColor: offset,
    },
  }),
  divideStyle: (offset: CSS["borderStyle"]) => ({
    "& > * + *": {
      borderStyle: offset,
    },
  }),
  divideX: (offset: CSS["borderLeft"]) => ({
    "& > * + *": {
      borderLeft: offset,
    },
  }),
  divideXWidth: (offset: CSS["borderLeftWidth"]) => ({
    "& > * + *": {
      borderLeftWidth: offset,
    },
  }),
  divideY: (offset: CSS["borderTop"]) => ({
    "& > * + *": {
      borderTop: offset,
    },
  }),
  divideYWidth: (offset: CSS["borderTopWidth"]) => ({
    "& > * + *": {
      borderTopWidth: offset,
    },
  }),

  // inset
  inset: (inset: CSS["top"]) => ({
    top: inset,
    right: inset,
    bottom: inset,
    left: inset,
  }),
  insetX: (inset: CSS["left"]) => ({ right: inset, left: inset }),
  insetY: (inset: CSS["top"]) => ({ top: inset, bottom: inset }),

  // margin
  m: (margin: CSS["margin"]) => ({ margin }),
  mt: (marginTop: CSS["margin"]) => ({ marginTop }),
  mr: (marginRight: CSS["marginRight"]) => ({ marginRight }),
  mb: (marginBottom: CSS["marginBottom"]) => ({ marginBottom }),
  ml: (marginLeft: CSS["marginLeft"]) => ({ marginLeft }),
  mx: (marginX: CSS["marginLeft"]) => ({
    marginLeft: marginX,
    marginRight: marginX,
  }),
  my: (marginY: CSS["marginTop"]) => ({
    marginTop: marginY,
    marginBottom: marginY,
  }),
  mil: (marginInline: CSS["marginInline"]) => ({ marginInline }),
  mbl: (marginBlock: CSS["marginBlock"]) => ({ marginBlock }),
  spaceX: (space: CSS["margin"]) => ({
    "& > * + *": { marginLeft: space },
  }),
  spaceY: (space: CSS["margin"]) => ({
    "& > * + *": { marginTop: space },
  }),

  // padding
  p: (padding: CSS["padding"]) => ({ padding }),
  pt: (paddingTop: CSS["paddingTop"]) => ({ paddingTop }),
  pr: (paddingRight: CSS["paddingRight"]) => ({ paddingRight }),
  pb: (paddingBottom: CSS["paddingBottom"]) => ({
    paddingBottom,
  }),
  pl: (paddingLeft: CSS["paddingLeft"]) => ({ paddingLeft }),
  px: (paddingX: CSS["paddingLeft"]) => ({
    paddingLeft: paddingX,
    paddingRight: paddingX,
  }),
  py: (paddingY: CSS["paddingTop"]) => ({
    paddingTop: paddingY,
    paddingBottom: paddingY,
  }),

  // size
  w: (width: CSS["width"]) => ({ width }),
  minW: (minWidth: CSS["minWidth"]) => ({ minWidth }),
  maxW: (maxWidth: CSS["maxWidth"]) => ({ maxWidth }),
  h: (height: CSS["height"]) => ({ height }),
  minH: (minHeight: CSS["minHeight"]) => ({ minHeight }),
  maxH: (maxHeight: CSS["maxHeight"]) => ({ maxHeight }),
  size: (size: CSS["width"]) => ({
    width: size,
    height: size,
  }),
  minSize: (size: CSS["minWidth"]) => ({
    minWidth: size,
    minHeight: size,
  }),
  maxSize: (size: CSS["maxWidth"]) => ({
    maxWidth: size,
    maxHeight: size,
  }),

  // Flex & Grid
  colEnd: (gridColumnEnd: CSS["gridColumnEnd"]) => ({
    gridColumnEnd,
  }),
  colSpan: (cols: number | "full" | "auto") => {
    if (cols === "auto") {
      return {
        gridColumn: "auto",
      };
    }
    if (cols === "full") {
      return {
        gridColumn: "1 / -1",
      };
    }
    return {
      gridColumn: `span ${cols} / span ${cols}`,
    };
  },
  colStart: (gridColumnStart: CSS["gridColumnStart"]) => ({
    gridColumnStart,
  }),
  flexDir: (flexDirection: CSS["flexDirection"]) => ({
    flexDirection,
  }),
  gridCols: (cols: number | "none" | (string & {})) =>
    typeof cols === "number"
      ? { gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }
      : { gridTemplateColumns: cols },
  gridRows: (rows: number | "none" | (string & {})) =>
    typeof rows === "number"
      ? { gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))` }
      : { gridTemplateRows: rows },
  items: (items: CSS["alignItems"]) => ({
    alignItems: items,
  }),
  justify: (justify: CSS["justifyContent"]) => ({
    justifyContent: justify,
  }),
  rowEnd: (gridRowEnd: CSS["gridRowEnd"]) => ({
    gridRowEnd,
  }),
  rowSpan: (rows: number | "full" | "auto") => {
    if (rows === "auto") {
      return {
        gridRow: "auto",
      };
    }
    if (rows === "full") {
      return {
        gridRow: "1 / -1",
      };
    }
    return {
      gridRow: `span ${rows} / span ${rows}`,
    };
  },
  rowStart: (gridRowStart: CSS["gridRowStart"]) => ({
    gridRowStart,
  }),

  // Typography
  align: (align: CSS["textAlign"]) => ({
    textAlign: align,
  }),
  fontScale: (scale: number) => {
    const [fontSize, lineHeight] =
      fontScales[scale.toString()] || fontScales["1"];
    return {
      fontSize: rem(fontSize),
      lineHeight: em(lineHeight, fontSize),
      letterSpacing: `${getLetterSpacing(fontSize)}em`,
    };
  },
  lineClamp: (lineClamp: CSS["lineClamp"]) => ({
    "-webkit-line-clamp": lineClamp,
    display: "-webkit-box",
    "-webkit-box-orient": "vertical",
    overflow: "hidden",
  }),

  // SPECIALS - a.k.a. The ones that accept css as an argument
  print: (css: CSS) => ({
    "@media print": css,
  }),
  dark: (css: CSS) => ({
    '&[data-theme="dark"], [data-theme="dark"] &': css,
  }),
  light: (css: CSS) => ({
    '&[data-theme="light"], [data-theme="light"] &': css,
  }),
};

On this page