Stoop
GitHub
/

Utility Functions

Create custom shorthand properties that transform into CSS

Utility functions create shorthand properties that transform into CSS objects. They're applied before theme token resolution, enabling theme tokens in utility values.

How Utilities Work

  1. CSS object is processed
  2. Utility functions are applied (transform shorthand → CSS properties)
  3. Theme tokens are resolved
  4. CSS is generated and injected

Basic Setup

Define utility functions in your createStoop configuration:

import { createStoop } from "stoop";
import type { CSS, UtilityFunction } from "stoop";

type CSSPropertyValue = string | number;

const utils: Record<string, UtilityFunction> = {
  px: (value: CSSPropertyValue | CSS | undefined): CSS => {
    const val = typeof value === "string" || typeof value === "number"
      ? String(value)
      : "";
    return { paddingLeft: val, paddingRight: val };
  },
  py: (value: CSSPropertyValue | CSS | undefined): CSS => {
    const val = typeof value === "string" || typeof value === "number"
      ? String(value)
      : "";
    return { paddingTop: val, paddingBottom: val };
  },
};

const stoop = createStoop({
  theme: {
    space: {
      small: "8px",
      medium: "16px",
      large: "24px",
    },
  },
  utils,
});

export const { styled, css } = stoop;

Common Utility Examples

Spacing Utilities

const utils = {
  // Padding
  px: (value) => ({ paddingLeft: value, paddingRight: value }),
  py: (value) => ({ paddingTop: value, paddingBottom: value }),
  pt: (value) => ({ paddingTop: value }),
  pr: (value) => ({ paddingRight: value }),
  pb: (value) => ({ paddingBottom: value }),
  pl: (value) => ({ paddingLeft: value }),

  // Margin
  mx: (value) => ({ marginLeft: value, marginRight: value }),
  my: (value) => ({ marginTop: value, marginBottom: value }),
  mt: (value) => ({ marginTop: value }),
  mr: (value) => ({ marginRight: value }),
  mb: (value) => ({ marginBottom: value }),
  ml: (value) => ({ marginLeft: value }),
};

Usage with Theme Tokens

const Button = styled("button", {
  px: "$medium",  // → paddingLeft: "16px", paddingRight: "16px"
  py: "$small",   // → paddingTop: "8px", paddingBottom: "8px"
  mt: "$large",   // → marginTop: "24px"
});

Complex Utilities

Utilities return complex CSS objects, including nested selectors and media queries:

const utils = {
  hidden: (value: CSSPropertyValue | CSS | undefined): CSS => {
    const breakpoint = typeof value === "string" ? value : "";

    if (breakpoint === "mobile") {
      return {
        "@media (max-width: 768px)": {
          display: "none",
        },
      };
    }

    if (breakpoint === "desktop") {
      return {
        "@media (min-width: 769px)": {
          display: "none",
        },
      };
    }

    return { display: "none" };
  },

  // Responsive spacing using media query shortcuts
  // (Requires media queries defined in theme config)
  responsivePadding: (value) => ({
    padding: value,
    "@media (max-width: 768px)": {
      padding: "$small",
    },
    "@media (min-width: 769px)": {
      padding: "$large",
    },
  }),
};

Using Utilities

In Styled Components

const Box = styled("div", {
  px: "$medium",
  my: "$large",
  hidden: "mobile", // Hide on mobile breakpoint
});

In css() Function

const buttonClass = css({
  px: "$medium",
  py: "$small",
  mx: "auto",
});

With the css Prop

<Box css={{ px: "$large", mt: "$medium" }}>
  Content
</Box>

Utility Function Type

A utility function has the following signature:

type UtilityFunction = (
  value: CSSPropertyValue | CSS | undefined
) => CSS;

Best Practices

  1. Use theme tokens; utilities work great with theme tokens ($medium, $primary, etc.)
  2. Keep utilities simple; each utility should do one thing well
  3. Handle edge cases; handle undefined/null values gracefully
  4. Use TypeScript for type safety
  5. Document complex utilities with examples

Advanced Example

Complete example with multiple utilities:

import { createStoop } from "stoop";
import type { CSS, UtilityFunction } from "stoop";

type CSSPropertyValue = string | number;

const utils: Record<string, UtilityFunction> = {
  // Spacing
  px: (value) => {
    const val = String(value || "");
    return { paddingLeft: val, paddingRight: val };
  },
  py: (value) => {
    const val = String(value || "");
    return { paddingTop: val, paddingBottom: val };
  },
  mx: (value) => {
    const val = String(value || "");
    return { marginLeft: val, marginRight: val };
  },
  my: (value) => {
    const val = String(value || "");
    return { marginTop: val, marginBottom: val };
  },

  // Layout utilities (value parameter unused - these are boolean flags)
  flex: () => ({ display: "flex" }),
  grid: () => ({ display: "grid" }),

  // Visibility
  hidden: (value) => {
    if (value === "mobile") {
      return {
        "@media (max-width: 768px)": {
          display: "none",
        },
      };
    }
    return { display: "none" };
  },
};

const stoop = createStoop({
  theme: {
    space: { small: "8px", medium: "16px", large: "24px" },
  },
  utils,
});

export const { styled, css } = stoop;

// Usage
const Card = styled("div", {
  px: "$medium",
  py: "$large",
  mx: "auto",
  flex: undefined,  // Layout utility - no value needed
});

Notes