CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tss-react

Type safe CSS-in-JS API heavily inspired by react-jss

Pending
Overview
Eval results
Files

css-utilities.mddocs/

CSS Utilities

TSS-React provides low-level CSS generation and class manipulation utilities for advanced use cases, direct Emotion integration, and fine-grained styling control. These utilities form the foundation of the higher-level APIs.

Capabilities

CSS and CX Factory Function

Creates css and cx functions bound to a specific Emotion cache instance.

/**
 * Creates css and cx functions for a given Emotion cache
 * @param params - Configuration object with cache instance
 * @returns Object containing css and cx functions
 */
function createCssAndCx(params: { cache: EmotionCache }): {
  css: Css;
  cx: Cx;
};

interface EmotionCache {
  key: string;
  inserted: Record<string, string | boolean>;
  registered: Record<string, string>;
  sheet: StyleSheet;
  compat?: boolean;
  nonce?: string;
  insert: (rule: string, serialized: SerializedStyles) => void;
}

Usage Examples:

import createCache from "@emotion/cache";
import { createCssAndCx } from "tss-react/cssAndCx";

// Create custom cache
const cache = createCache({
  key: "my-styles",
  prepend: true
});

// Create css and cx functions
const { css, cx } = createCssAndCx({ cache });

// Use css function for dynamic styles
const dynamicClass = css({
  backgroundColor: "blue",
  color: "white",
  padding: 16,
  "&:hover": {
    backgroundColor: "darkblue"
  }
});

// Use cx function for combining classes
const combinedClass = cx(
  "base-class",
  dynamicClass,
  {
    "conditional-class": true,
    "disabled-class": false
  }
);

function MyComponent() {
  return <div className={combinedClass}>Styled content</div>;
}

CSS-and-CX Hook Factory

Creates a React hook that returns css and cx functions with cache integration.

/**
 * Creates a hook that returns css and cx functions
 * @param params - Configuration object with cache hook
 * @returns Object containing useCssAndCx hook
 */
function createUseCssAndCx(params: {
  useCache: () => EmotionCache;
}): {
  useCssAndCx(): { css: Css; cx: Cx };
};

Usage Examples:

import createCache from "@emotion/cache";
import { createUseCssAndCx } from "tss-react/cssAndCx";
import { useState } from "react";

// Create cache provider hook
function useMyCache() {
  const [cache] = useState(() => createCache({
    key: "dynamic-styles",
    prepend: true
  }));
  return cache;
}

// Create hook factory
const { useCssAndCx } = createUseCssAndCx({
  useCache: useMyCache
});

// Use in components
function DynamicStyledComponent({ variant }: { variant: "primary" | "secondary" }) {
  const { css, cx } = useCssAndCx();
  
  const baseStyles = css({
    padding: "12px 24px",
    borderRadius: "4px",
    border: "none",
    cursor: "pointer",
    fontSize: "16px",
    fontWeight: "500",
    transition: "all 0.2s ease"
  });
  
  const variantStyles = css(
    variant === "primary" 
      ? {
          backgroundColor: "#007bff",
          color: "white",
          "&:hover": { backgroundColor: "#0056b3" }
        }
      : {
          backgroundColor: "#6c757d", 
          color: "white",
          "&:hover": { backgroundColor: "#545b62" }
        }
  );
  
  return (
    <button className={cx(baseStyles, variantStyles)}>
      Click me
    </button>
  );
}

CSS Function Interface

Template literal and function-based CSS generation with Emotion serialization.

interface Css {
  /**
   * Template literal CSS generation
   * @param template - Template strings array
   * @param args - Interpolated CSS values
   * @returns Generated CSS class name
   */
  (template: TemplateStringsArray, ...args: CSSInterpolation[]): string;
  
  /**
   * Function-based CSS generation
   * @param args - CSS interpolation values
   * @returns Generated CSS class name
   */
  (...args: CSSInterpolation[]): string;
}

type CSSInterpolation = 
  | CSSObject 
  | string 
  | number 
  | false 
  | null 
  | undefined 
  | CSSInterpolation[];

Usage Examples:

import { css } from "my-css-instance";

// Template literal usage
const templateClass = css`
  background-color: ${"red"};
  color: white;
  padding: ${16}px;
  
  &:hover {
    background-color: ${"darkred"};
  }
  
  @media (max-width: 768px) {
    padding: ${8}px;
  }
`;

// Function usage with CSS object
const objectClass = css({
  backgroundColor: "blue",
  color: "white",
  padding: 16,
  borderRadius: 4,
  "&:hover": {
    backgroundColor: "darkblue",
    transform: "scale(1.02)"
  },
  "@media (max-width: 768px)": {
    padding: 8,
    fontSize: 14
  }
});

// Function usage with multiple arguments
const multiArgClass = css(
  { backgroundColor: "green", color: "white" },
  { padding: 12, borderRadius: 8 },
  {
    "&:active": {
      transform: "scale(0.98)"
    }
  }
);

// Dynamic CSS generation
function createButtonStyles(theme: any, variant: string) {
  return css({
    backgroundColor: theme.colors[variant],
    color: theme.textColors[variant],
    padding: theme.spacing.md,
    borderRadius: theme.borderRadius,
    border: "none",
    cursor: "pointer",
    fontSize: theme.fontSizes.md,
    fontWeight: theme.fontWeights.semibold,
    transition: "all 0.2s ease",
    "&:hover": {
      backgroundColor: theme.colors[`${variant}Hover`],
      boxShadow: theme.shadows.md
    },
    "&:focus": {
      outline: `2px solid ${theme.colors.focus}`,
      outlineOffset: "2px"
    },
    "&:disabled": {
      opacity: 0.5,
      cursor: "not-allowed"
    }
  });
}

CX Function Interface

Classname combination utility with conditional logic support.

/**
 * Combines multiple class names with conditional logic
 * @param classNames - Array of class name arguments
 * @returns Combined class name string
 */
type Cx = (...classNames: CxArg[]) => string;

type CxArg = 
  | string 
  | number 
  | boolean 
  | undefined 
  | null 
  | { [className: string]: boolean | undefined | null }
  | CxArg[];

Usage Examples:

import { cx } from "my-cx-instance";

// Basic class combination
const basicClasses = cx("btn", "btn-primary", "btn-large");
// Result: "btn btn-primary btn-large"

// Conditional classes with object syntax
const conditionalClasses = cx(
  "btn",
  {
    "btn-primary": variant === "primary",
    "btn-secondary": variant === "secondary",
    "btn-disabled": disabled,
    "btn-loading": loading
  }
);

// Mixed arguments
const mixedClasses = cx(
  "base-class",
  dynamicClass,
  {
    "active": isActive,
    "highlighted": isHighlighted
  },
  extraClass && "extra-class",
  ["array", "of", "classes"]
);

// Real-world component example
function Button({ 
  variant = "primary",
  size = "medium", 
  disabled = false,
  loading = false,
  className,
  children,
  ...props 
}: ButtonProps) {
  const { css, cx } = useCssAndCx();
  
  const baseStyles = css({
    border: "none",
    borderRadius: 4,
    cursor: "pointer",
    fontWeight: 500,
    transition: "all 0.2s ease",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center"
  });
  
  const sizeStyles = css({
    padding: {
      small: "6px 12px",
      medium: "8px 16px", 
      large: "12px 24px"
    }[size],
    fontSize: {
      small: "14px",
      medium: "16px",
      large: "18px"
    }[size]
  });
  
  const variantStyles = css(
    variant === "primary" 
      ? { backgroundColor: "#007bff", color: "white" }
      : { backgroundColor: "#6c757d", color: "white" }
  );
  
  const buttonClass = cx(
    baseStyles,
    sizeStyles, 
    variantStyles,
    {
      [css({ opacity: 0.5, cursor: "not-allowed" })]: disabled,
      [css({ position: "relative", "&::after": { content: '"..."' } })]: loading
    },
    className
  );
  
  return (
    <button 
      className={buttonClass}
      disabled={disabled || loading}
      {...props}
    >
      {children}
    </button>
  );
}

Class Merging Utility

Utility function for merging style classes with override support, particularly useful for component libraries.

/**
 * Merges classes from useStyles with override classes
 * @param classesFromUseStyles - Classes generated by TSS-React hooks
 * @param classesOverrides - Optional override classes from props
 * @param cx - Class combination function
 * @returns Merged classes object with overrides applied
 */
function mergeClasses<T extends string, U extends string>(
  classesFromUseStyles: Record<T, string>,
  classesOverrides: Partial<Record<U, string>> | undefined,
  cx: Cx
): Record<T, string> & Partial<Record<Exclude<U, T>, string>>;

Usage Examples:

import { mergeClasses } from "tss-react";
import { tss } from "tss-react";

// Component with style overrides support
interface CardProps {
  title: string;
  children: React.ReactNode;
  classes?: {
    root?: string;
    title?: string;
    content?: string;
    footer?: string;
  };
}

const useCardStyles = tss.create({
  root: {
    backgroundColor: "white",
    borderRadius: 8,
    padding: 16,
    boxShadow: "0 2px 4px rgba(0,0,0,0.1)"
  },
  title: {
    fontSize: 18,
    fontWeight: "bold",
    marginBottom: 12,
    color: "#333"
  },
  content: {
    color: "#666",
    lineHeight: 1.5
  }
});

function Card({ title, children, classes: classesOverrides }: CardProps) {
  const { classes: defaultClasses, cx } = useCardStyles();
  
  // Merge default classes with overrides
  const classes = mergeClasses(defaultClasses, classesOverrides, cx);
  
  return (
    <div className={classes.root}>
      <h3 className={classes.title}>{title}</h3>
      <div className={classes.content}>{children}</div>
    </div>
  );
}

// Usage with overrides
function App() {
  return (
    <Card
      title="Custom Card"
      classes={{
        root: "custom-card-root",
        title: "custom-card-title",
        // Additional classes not in original component
        footer: "custom-footer-class"
      }}
    >
      <p>Card content here</p>
    </Card>
  );
}

// Component library pattern
const useLibraryStyles = tss
  .withParams<{ variant: "outlined" | "filled"; size: "small" | "large" }>()
  .create(({ theme }, { variant, size }) => ({
    root: {
      padding: size === "small" ? 8 : 16,
      borderRadius: 4,
      ...(variant === "outlined" && {
        border: "1px solid #ccc",
        backgroundColor: "transparent"
      }),
      ...(variant === "filled" && {
        backgroundColor: "#f5f5f5",
        border: "none"
      })
    },
    label: {
      fontSize: size === "small" ? 14 : 16,
      fontWeight: 500
    }
  }));

interface LibraryComponentProps {
  variant: "outlined" | "filled";
  size: "small" | "large";
  label: string;
  classes?: {
    root?: string;
    label?: string;
    icon?: string;
  };
}

function LibraryComponent({ 
  variant, 
  size, 
  label, 
  classes: classesOverrides 
}: LibraryComponentProps) {
  const { classes: defaultClasses, cx } = useLibraryStyles({ variant, size });
  const classes = mergeClasses(defaultClasses, classesOverrides, cx);
  
  return (
    <div className={classes.root}>
      <span className={classes.label}>{label}</span>
      {classes.icon && <span className={classes.icon}>→</span>}
    </div>
  );
}

Advanced Patterns

Custom Cache Strategies

import createCache from "@emotion/cache";
import { createCssAndCx } from "tss-react/cssAndCx";

// Performance-optimized cache
const performanceCache = createCache({
  key: "perf",
  speedy: true, // No text content in style elements (faster)
  prepend: true // Insert at beginning of head
});

// Development cache with debugging
const devCache = createCache({
  key: "dev",
  speedy: false, // Include text content for debugging
  nonce: process.env.CSP_NONCE
});

const { css: perfCss, cx: perfCx } = createCssAndCx({ 
  cache: process.env.NODE_ENV === "production" ? performanceCache : devCache 
});

Dynamic Style Generation

function createDynamicStyles(theme: any) {
  const { css, cx } = createCssAndCx({ cache: theme.cache });
  
  return {
    // Generate styles based on theme configuration
    generateButtonStyle: (variant: string, size: string) => css({
      backgroundColor: theme.palette[variant].main,
      color: theme.palette[variant].contrastText,
      padding: theme.spacing[size],
      borderRadius: theme.shape.borderRadius,
      fontSize: theme.typography[size].fontSize,
      "&:hover": {
        backgroundColor: theme.palette[variant].dark
      }
    }),
    
    // Combine multiple dynamic styles
    combineStyles: (...styles: string[]) => cx(...styles),
    
    // Generate responsive utilities
    generateResponsiveStyle: (breakpoints: Record<string, any>) => css(
      Object.entries(breakpoints).reduce((acc, [bp, styles]) => ({
        ...acc,
        [`@media (min-width: ${theme.breakpoints[bp]})`]: styles
      }), {})
    )
  };
}

Install with Tessl CLI

npx tessl i tessl/npm-tss-react

docs

compatibility.md

core-tss-api.md

css-utilities.md

dsfr-integration.md

global-styles-keyframes.md

index.md

makestyles-api.md

mui-integration.md

nextjs-ssr.md

withstyles-hoc.md

tile.json