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

global-styles-keyframes.mddocs/

Global Styles & Keyframes

TSS-React provides utilities for global CSS injection and animation keyframe definitions, built on Emotion's proven CSS-in-JS infrastructure. These tools enable application-wide styling and complex animations.

Capabilities

Global Styles Component

React component for injecting global CSS styles into the document head.

/**
 * React component for injecting global CSS styles
 * @param props - Props containing the global styles definition
 * @returns JSX element that injects global styles
 */
function GlobalStyles(props: { styles: CSSInterpolation }): JSX.Element;

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

interface CSSObject {
  [property: string]: CSSInterpolation;
  label?: string;
}

Usage Examples:

import { GlobalStyles } from "tss-react";

// Basic global styles
function App() {
  return (
    <>
      <GlobalStyles
        styles={{
          body: {
            margin: 0,
            padding: 0,
            fontFamily: '"Helvetica Neue", Arial, sans-serif',
            backgroundColor: "#f5f5f5",
            color: "#333"
          },
          "*": {
            boxSizing: "border-box"
          },
          "*, *::before, *::after": {
            boxSizing: "border-box"
          }
        }}
      />
      <MyAppContent />
    </>
  );
}

// Theme-based global styles
function ThemedApp({ theme }: { theme: any }) {
  return (
    <>
      <GlobalStyles
        styles={{
          body: {
            backgroundColor: theme.palette.background.default,
            color: theme.palette.text.primary,
            fontFamily: theme.typography.fontFamily,
            fontSize: theme.typography.body1.fontSize,
            lineHeight: theme.typography.body1.lineHeight
          },
          a: {
            color: theme.palette.primary.main,
            textDecoration: "none",
            "&:hover": {
              textDecoration: "underline"
            }
          },
          "h1, h2, h3, h4, h5, h6": {
            fontWeight: theme.typography.fontWeightBold,
            margin: "0 0 16px 0"
          }
        }}
      />
      <AppContent />
    </>
  );
}

// CSS Reset with GlobalStyles
function CSSReset() {
  return (
    <GlobalStyles
      styles={{
        // Modern CSS reset
        "*, *::before, *::after": {
          boxSizing: "border-box"
        },
        "*": {
          margin: 0
        },
        "html, body": {
          height: "100%"
        },
        body: {
          lineHeight: 1.5,
          "-webkit-font-smoothing": "antialiased"
        },
        "img, picture, video, canvas, svg": {
          display: "block",
          maxWidth: "100%"
        },
        "input, button, textarea, select": {
          font: "inherit"
        },
        "p, h1, h2, h3, h4, h5, h6": {
          overflowWrap: "break-word"
        },
        "#root, #__next": {
          isolation: "isolate"
        }
      }}
    />
  );
}

// Multiple GlobalStyles components
function MultiGlobalStyles() {
  return (
    <>
      {/* Base reset */}
      <GlobalStyles
        styles={{
          "*": { boxSizing: "border-box" },
          body: { margin: 0, fontFamily: "system-ui" }
        }}
      />
      
      {/* Custom scrollbar */}
      <GlobalStyles
        styles={{
          "::-webkit-scrollbar": {
            width: 8
          },
          "::-webkit-scrollbar-track": {
            backgroundColor: "#f1f1f1"
          },
          "::-webkit-scrollbar-thumb": {
            backgroundColor: "#888",
            borderRadius: 4,
            "&:hover": {
              backgroundColor: "#555"
            }
          }
        }}
      />
      
      {/* Print styles */}
      <GlobalStyles
        styles={{
          "@media print": {
            "*": {
              color: "black !important",
              backgroundColor: "white !important"
            },
            ".no-print": {
              display: "none !important"
            }
          }
        }}
      />
      
      <AppContent />
    </>
  );
}

Keyframes Function

Function for defining CSS animation keyframes, re-exported from @emotion/react.

/**
 * Creates CSS animation keyframes (template literal version)
 * @param template - Template strings array containing keyframe definitions
 * @param args - Interpolated values for keyframes
 * @returns Animation name string for use in CSS animations
 */
function keyframes(
  template: TemplateStringsArray, 
  ...args: CSSInterpolation[]
): string;

/**
 * Creates CSS animation keyframes (function version)
 * @param args - CSS interpolation values containing keyframe definitions
 * @returns Animation name string for use in CSS animations
 */
function keyframes(...args: CSSInterpolation[]): string;

Usage Examples:

import { keyframes, tss } from "tss-react";

// Template literal keyframes
const fadeIn = keyframes`
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
`;

const slideIn = keyframes`
  0% {
    transform: translateX(-100%);
    opacity: 0;
  }
  50% {
    opacity: 0.5;
  }
  100% {
    transform: translateX(0);
    opacity: 1;
  }
`;

// Object-based keyframes
const bounce = keyframes({
  "0%, 20%, 53%, 80%, 100%": {
    transform: "translate3d(0, 0, 0)"
  },
  "40%, 43%": {
    transform: "translate3d(0, -30px, 0)"
  },
  "70%": {
    transform: "translate3d(0, -15px, 0)"
  },
  "90%": {
    transform: "translate3d(0, -4px, 0)"
  }
});

const pulse = keyframes({
  "0%": {
    transform: "scale(1)",
    opacity: 1
  },
  "50%": {
    transform: "scale(1.05)",
    opacity: 0.8
  },
  "100%": {
    transform: "scale(1)",
    opacity: 1
  }
});

// Using keyframes in TSS styles
const useAnimatedStyles = tss
  .withParams<{ 
    isVisible: boolean; 
    animationType: "fade" | "slide" | "bounce";
  }>()
  .create(({}, { isVisible, animationType }) => {
    const animations = {
      fade: fadeIn,
      slide: slideIn,
      bounce: bounce
    };
    
    return {
      root: {
        animation: isVisible 
          ? `${animations[animationType]} 0.5s ease-out forwards`
          : "none",
        opacity: isVisible ? 1 : 0
      },
      pulsingButton: {
        animation: `${pulse} 2s infinite`,
        cursor: "pointer",
        border: "none",
        borderRadius: 4,
        padding: "12px 24px",
        backgroundColor: "#007bff",
        color: "white",
        fontSize: 16
      }
    };
  });

function AnimatedComponent({ 
  isVisible, 
  animationType 
}: { 
  isVisible: boolean;
  animationType: "fade" | "slide" | "bounce";
}) {
  const { classes } = useAnimatedStyles({ isVisible, animationType });
  
  return (
    <div className={classes.root}>
      <h2>Animated Content</h2>
      <p>This content animates based on the selected animation type.</p>
      <button className={classes.pulsingButton}>
        Pulsing Button
      </button>
    </div>
  );
}

Complex Animation Patterns

Loading Animations

import { keyframes, tss } from "tss-react";

// Spinner keyframes
const spin = keyframes({
  "0%": { transform: "rotate(0deg)" },
  "100%": { transform: "rotate(360deg)" }
});

const dots = keyframes({
  "0%, 80%, 100%": {
    transform: "scale(0)",
    opacity: 0.5
  },
  "40%": {
    transform: "scale(1)",
    opacity: 1
  }
});

const shimmer = keyframes({
  "0%": {
    backgroundPosition: "-200px 0"
  },
  "100%": {
    backgroundPosition: "calc(200px + 100%) 0"
  }
});

const useLoadingStyles = tss.create({
  spinner: {
    width: 40,
    height: 40,
    border: "4px solid #f3f3f3",
    borderTop: "4px solid #3498db",
    borderRadius: "50%",
    animation: `${spin} 1s linear infinite`
  },
  
  dotsLoader: {
    display: "inline-block",
    position: "relative",
    width: 64,
    height: 64,
    "& div": {
      position: "absolute",
      top: 27,
      width: 11,
      height: 11,
      borderRadius: "50%",
      backgroundColor: "#3498db",
      animationTimingFunction: "cubic-bezier(0, 1, 1, 0)"
    },
    "& div:nth-child(1)": {
      left: 6,
      animation: `${dots} 0.6s infinite`
    },
    "& div:nth-child(2)": {
      left: 6,
      animation: `${dots} 0.6s infinite`,
      animationDelay: "-0.2s"
    },
    "& div:nth-child(3)": {
      left: 26,
      animation: `${dots} 0.6s infinite`,
      animationDelay: "-0.4s"
    }
  },
  
  shimmerCard: {
    background: "#f6f7f8",
    backgroundImage: `linear-gradient(
      90deg,
      #f6f7f8 0px,
      rgba(255, 255, 255, 0.8) 40px,
      #f6f7f8 80px
    )`,
    backgroundSize: "200px 100%",
    backgroundRepeat: "no-repeat",
    borderRadius: 4,
    display: "inline-block",
    lineHeight: 1,
    width: "100%",
    animation: `${shimmer} 1.2s ease-in-out infinite`
  }
});

function LoadingComponents() {
  const { classes } = useLoadingStyles();
  
  return (
    <div>
      <div className={classes.spinner} />
      <div className={classes.dotsLoader}>
        <div></div>
        <div></div>
        <div></div>
      </div>
      <div className={classes.shimmerCard} style={{ height: 200 }} />
    </div>
  );
}

Interactive Animations

import { keyframes, tss } from "tss-react";

// Interactive animation keyframes
const wiggle = keyframes({
  "0%, 7%": { transform: "rotateZ(0)" },
  "15%": { transform: "rotateZ(-15deg)" },
  "20%": { transform: "rotateZ(10deg)" },
  "25%": { transform: "rotateZ(-10deg)" },
  "30%": { transform: "rotateZ(6deg)" },
  "35%": { transform: "rotateZ(-4deg)" },
  "40%, 100%": { transform: "rotateZ(0)" }
});

const heartbeat = keyframes({
  "0%": { transform: "scale(1)" },
  "14%": { transform: "scale(1.3)" },
  "28%": { transform: "scale(1)" },
  "42%": { transform: "scale(1.3)" },
  "70%": { transform: "scale(1)" }
});

const rubber = keyframes({
  "0%": { transform: "scale3d(1, 1, 1)" },
  "30%": { transform: "scale3d(1.25, 0.75, 1)" },
  "40%": { transform: "scale3d(0.75, 1.25, 1)" },
  "50%": { transform: "scale3d(1.15, 0.85, 1)" },
  "65%": { transform: "scale3d(0.95, 1.05, 1)" },
  "75%": { transform: "scale3d(1.05, 0.95, 1)" },
  "100%": { transform: "scale3d(1, 1, 1)" }
});

const useInteractiveStyles = tss
  .withParams<{ 
    isHovered: boolean; 
    isClicked: boolean; 
    animationStyle: "wiggle" | "heartbeat" | "rubber";
  }>()
  .create(({}, { isHovered, isClicked, animationStyle }) => {
    const animations = {
      wiggle: wiggle,
      heartbeat: heartbeat,
      rubber: rubber
    };
    
    const durations = {
      wiggle: "0.5s",
      heartbeat: "1.2s",
      rubber: "1s"
    };
    
    return {
      interactiveButton: {
        padding: "12px 24px",
        backgroundColor: "#28a745",
        color: "white",
        border: "none",
        borderRadius: 8,
        cursor: "pointer",
        fontSize: 16,
        fontWeight: 600,
        transition: "all 0.2s ease",
        transform: isClicked ? "scale(0.95)" : "scale(1)",
        animation: isHovered 
          ? `${animations[animationStyle]} ${durations[animationStyle]} ease-in-out`
          : "none",
        "&:hover": {
          backgroundColor: "#218838",
          boxShadow: "0 4px 8px rgba(0,0,0,0.2)"
        }
      }
    };
  });

function InteractiveButton({ 
  animationStyle = "wiggle",
  children,
  onClick 
}: {
  animationStyle?: "wiggle" | "heartbeat" | "rubber";
  children: React.ReactNode;
  onClick?: () => void;
}) {
  const [isHovered, setIsHovered] = useState(false);
  const [isClicked, setIsClicked] = useState(false);
  
  const { classes } = useInteractiveStyles({ 
    isHovered, 
    isClicked, 
    animationStyle 
  });
  
  const handleClick = () => {
    setIsClicked(true);
    setTimeout(() => setIsClicked(false), 150);
    onClick?.();
  };
  
  return (
    <button
      className={classes.interactiveButton}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      onClick={handleClick}
    >
      {children}
    </button>
  );
}

Advanced Global Styling Patterns

CSS Custom Properties (CSS Variables)

import { GlobalStyles } from "tss-react";

function CSSVariablesSetup({ theme }: { theme: any }) {
  return (
    <GlobalStyles
      styles={{
        ":root": {
          // Color palette
          "--color-primary": theme.palette.primary.main,
          "--color-primary-dark": theme.palette.primary.dark,
          "--color-primary-light": theme.palette.primary.light,
          "--color-secondary": theme.palette.secondary.main,
          "--color-error": theme.palette.error.main,
          "--color-warning": theme.palette.warning.main,
          "--color-success": theme.palette.success.main,
          
          // Spacing scale
          "--spacing-xs": theme.spacing(0.5),
          "--spacing-sm": theme.spacing(1),
          "--spacing-md": theme.spacing(2),
          "--spacing-lg": theme.spacing(3),
          "--spacing-xl": theme.spacing(4),
          
          // Typography
          "--font-family": theme.typography.fontFamily,
          "--font-size-sm": theme.typography.body2.fontSize,
          "--font-size-md": theme.typography.body1.fontSize,
          "--font-size-lg": theme.typography.h6.fontSize,
          
          // Shadows and elevation
          "--shadow-sm": theme.shadows[1],
          "--shadow-md": theme.shadows[4],
          "--shadow-lg": theme.shadows[8],
          
          // Border radius
          "--border-radius": theme.shape.borderRadius,
          "--border-radius-lg": theme.shape.borderRadius * 2,
          
          // Transitions
          "--transition-fast": "0.15s ease",
          "--transition-normal": "0.3s ease",
          "--transition-slow": "0.5s ease"
        },
        
        // Dark mode variables
        "[data-theme='dark']": {
          "--color-background": "#1a1a1a",
          "--color-surface": "#2d2d2d",
          "--color-text": "#ffffff",
          "--color-text-secondary": "#b3b3b3"
        },
        
        // Light mode variables
        "[data-theme='light']": {
          "--color-background": "#ffffff",
          "--color-surface": "#f5f5f5",
          "--color-text": "#333333",
          "--color-text-secondary": "#666666"
        }
      }}
    />
  );
}

// Using CSS variables in TSS styles
const useCSSVariableStyles = tss.create({
  card: {
    backgroundColor: "var(--color-surface)",
    color: "var(--color-text)",
    padding: "var(--spacing-lg)",
    borderRadius: "var(--border-radius-lg)",
    boxShadow: "var(--shadow-md)",
    transition: "var(--transition-normal)",
    "&:hover": {
      boxShadow: "var(--shadow-lg)",
      transform: "translateY(-2px)"
    }
  },
  
  button: {
    backgroundColor: "var(--color-primary)",
    color: "white",
    border: "none",
    padding: "var(--spacing-sm) var(--spacing-md)",
    borderRadius: "var(--border-radius)",
    cursor: "pointer",
    fontSize: "var(--font-size-md)",
    transition: "var(--transition-fast)",
    "&:hover": {
      backgroundColor: "var(--color-primary-dark)"
    }
  }
});

Responsive Global Styles

import { GlobalStyles } from "tss-react";

function ResponsiveGlobalStyles({ theme }: { theme: any }) {
  return (
    <GlobalStyles
      styles={{
        // Base typography scale
        html: {
          fontSize: 14,
          [theme.breakpoints.up("sm")]: {
            fontSize: 16
          },
          [theme.breakpoints.up("lg")]: {
            fontSize: 18
          }
        },
        
        // Container widths
        ".container": {
          width: "100%",
          maxWidth: "100%",
          paddingLeft: theme.spacing(2),
          paddingRight: theme.spacing(2),
          marginLeft: "auto",
          marginRight: "auto",
          [theme.breakpoints.up("sm")]: {
            maxWidth: 540,
            paddingLeft: theme.spacing(3),
            paddingRight: theme.spacing(3)
          },
          [theme.breakpoints.up("md")]: {
            maxWidth: 720
          },
          [theme.breakpoints.up("lg")]: {
            maxWidth: 960
          },
          [theme.breakpoints.up("xl")]: {
            maxWidth: 1140
          }
        },
        
        // Responsive grid system
        ".grid": {
          display: "grid",
          gap: theme.spacing(2),
          gridTemplateColumns: "1fr",
          [theme.breakpoints.up("sm")]: {
            gridTemplateColumns: "repeat(2, 1fr)"
          },
          [theme.breakpoints.up("md")]: {
            gridTemplateColumns: "repeat(3, 1fr)",
            gap: theme.spacing(3)
          },
          [theme.breakpoints.up("lg")]: {
            gridTemplateColumns: "repeat(4, 1fr)"
          }
        },
        
        // Responsive utilities
        ".hide-mobile": {
          display: "none",
          [theme.breakpoints.up("md")]: {
            display: "block"
          }
        },
        
        ".hide-desktop": {
          display: "block",
          [theme.breakpoints.up("md")]: {
            display: "none"
          }
        }
      }}
    />
  );
}

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