Skip to main content
Bun’s bundler has built-in support for CSS with the following features:
  • Transpile modern/future features for compatibility with all browsers (including vendor prefixes)
  • Minification
  • CSS Modules
  • Tailwind (via native bundler plugin)

Transpilation

Bun’s CSS bundler lets you use modern/future CSS features without worrying about browser compatibility—thanks to transpilation and vendor prefixing enabled by default. Bun’s CSS parser and bundler is a direct port of LightningCSS from Rust to Zig, with bundling methodology inspired by esbuild. The transpiler converts modern CSS syntax into backwards-compatible equivalents for a range of browsers.
Special thanks to the authors of LightningCSS and esbuild for their excellent work.

Browser compatibility

By default, Bun’s CSS bundler targets browsers that support:
  • ES2020
  • Edge 88+
  • Firefox 78+
  • Chrome 87+
  • Safari 14+

Syntax downleveling

Nesting

The CSS nesting specification allows you to write more concise and intuitive stylesheets by nesting selectors within each other. Instead of repeating parent selectors throughout your CSS file, you can write child styles directly inside parent blocks.
styles.css
/* Using nesting */
.card {
  background: white;
  border-radius: 4px;

  .title {
    font-size: 1.2rem;
    font-weight: bold;
  }

  .content {
    padding: 1rem;
  }
}
Bun’s CSS bundler automatically converts this nested syntax into traditional flat CSS for compatibility with all browsers:
styles.css
/* Compiled output */
.card {
  background: white;
  border-radius: 4px;
}

.card .title {
  font-size: 1.2rem;
  font-weight: bold;
}

.card .content {
  padding: 1rem;
}
You can also nest media queries and other at-rules within selectors, avoiding repeated selector patterns:
styles.css
.responsive-element {
  display: block;

  @media (min-width: 768px) {
    display: flex;
  }
}
Compiles to:
styles.css
.responsive-element {
  display: block;
}

@media (min-width: 768px) {
  .responsive-element {
    display: flex;
  }
}

Color mixing

The color-mix() function lets you easily blend two colors in a specified proportion within a chosen color space. This powerful feature lets you create color variations without manually calculating result values.
styles.css
.button {
  /* Mix blue and red at 30%:70% in RGB color space */
  background-color: color-mix(in srgb, blue 30%, red);

  /* Create a lighter variant for hover state */
  &:hover {
    background-color: color-mix(in srgb, blue 30%, red, white 20%);
  }
}
Bun’s CSS bundler evaluates these color mixes at build time (when all color values are known, not CSS variables), generating static color values that work in all browsers:
styles.css
.button {
  /* The exact computed result color */
  background-color: #b31a1a;
}

.button:hover {
  background-color: #c54747;
}
This feature is particularly useful for creating programmatic color systems like tints, shades, and accents without a preprocessor or custom tooling.

Relative colors

CSS now allows you to modify individual components of colors using relative color syntax. This powerful feature lets you create color variants by adjusting lightness, saturation, or individual channels without recalculating the entire color.
styles.css
.theme-color {
  /* Start with a base color and increase its lightness by 15% */
  --accent: lch(from purple calc(l + 15%) c h);

  /* Take our brand blue and make a desaturated version */
  --subtle-blue: oklch(from var(--brand-blue) l calc(c * 0.8) h);
}
Bun’s CSS bundler calculates these relative color modifications at build time (when not using CSS variables), generating static color values for browsers:
.theme-color {
  --accent: lch(69.32% 58.34 328.37);
  --subtle-blue: oklch(60.92% 0.112 240.01);
}
This is excellent for theme generation, creating accessible color variants, or building color scales based on mathematical relationships rather than hard-coded values.

LAB colors

Modern CSS supports perceptually uniform color spaces like LAB, LCH, OKLAB, and OKLCH, which offer significant advantages over traditional RGB. These color spaces can represent colors beyond the standard RGB gamut, bringing more vibrant and visually consistent designs.
styles.css
.vibrant-element {
  /* A vibrant red that's outside the sRGB gamut boundaries */
  color: lab(55% 78 35);

  /* Smooth gradient using perceptual color space */
  background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg));
}
Bun’s CSS bundler automatically converts these advanced color formats to backwards-compatible fallbacks for browsers that haven’t implemented them yet:
styles.css
.vibrant-element {
  /* Fallback to closest RGB approximation */
  color: #ff0f52;
  /* P3 fallback for browsers with wider gamut support */
  color: color(display-p3 1 0.12 0.37);
  /* Original value for browsers that support the format */
  color: lab(55% 78 35);

  background: linear-gradient(to right, #cd4e15, #3887ab);
  background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg));
}
This layered approach ensures the best color rendering across all browsers while letting you use the latest color technology in your designs.

color function

The color() function provides a standardized way to specify colors using various predefined color spaces, expanding your design options beyond traditional RGB space. This enables access to wider color gamuts, creating more vibrant designs.
styles.css
.vivid-element {
  /* Use Display P3 color space for wider gamut */
  color: color(display-p3 1 0.1 0.3);

  /* Use A98 RGB color space */
  background-color: color(a98-rgb 0.44 0.5 0.37);
}
Bun’s CSS bundler provides appropriate fallbacks:
styles.css
.vivid-element {
  color: #ff0c52;
  color: color(display-p3 1 0.1 0.3);

  background-color: #648464;
  background-color: color(a98-rgb 0.44 0.5 0.37);
}

CSS Modules

CSS Modules provide local scoping for CSS by default, preventing naming conflicts and making styles more maintainable.
Button.module.css
.primary {
  background-color: blue;
  color: white;
}

.secondary {
  background-color: gray;
  color: black;
}
Button.tsx
import styles from "./Button.module.css";

export function Button({ variant }) {
  return (
    <button className={styles[variant]}>
      Click me
    </button>
  );
}
The CSS bundler will:
  1. Generate unique class names
  2. Export a mapping object
  3. Bundle the transformed CSS
Output
._Button_primary_1a2b3c {
  background-color: blue;
  color: white;
}

._Button_secondary_4d5e6f {
  background-color: gray;
  color: black;
}

Importing CSS

CSS can be imported in JavaScript/TypeScript files:
app.ts
import "./styles.css";
All imported CSS files are bundled into a single CSS file in the output directory:
.
├── app.ts
├── styles.css
└── out
    ├── app.js
    └── app.css

@import statements

CSS @import statements are resolved and bundled:
main.css
@import "./reset.css";
@import "./theme.css";

body {
  font-family: sans-serif;
}
All imports are inlined into a single CSS file.

url() references

Asset references in url() are resolved and copied to the output directory:
styles.css
.background {
  background-image: url("./image.png");
}

@font-face {
  font-family: "CustomFont";
  src: url("./font.woff2") format("woff2");
}
Assets are hashed and copied:
Output
.background {
  background-image: url("./image-a1b2c3.png");
}

@font-face {
  font-family: "CustomFont";
  src: url("./font-d4e5f6.woff2") format("woff2");
}

Minification

Enable CSS minification:
bun build ./app.ts --outdir ./out --minify
This removes whitespace, shortens color values, and optimizes the CSS.

Vendor prefixes

Vendor prefixes are automatically added based on the browser targets:
Input
.box {
  display: flex;
  user-select: none;
}
Output
.box {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

Custom properties

CSS custom properties (variables) are preserved:
:root {
  --primary-color: blue;
  --spacing: 1rem;
}

.button {
  background-color: var(--primary-color);
  padding: var(--spacing);
}

Media queries

Media queries are preserved and can be nested:
.container {
  width: 100%;

  @media (min-width: 768px) {
    width: 750px;
  }

  @media (min-width: 1024px) {
    width: 960px;
  }
}

CSS chunking

When code splitting is enabled, CSS is also split into chunks:
bun build ./entry-a.ts ./entry-b.ts --outdir ./out --splitting
Shared CSS is extracted into separate chunk files.

Tailwind CSS

Bun provides a native bundler plugin for Tailwind CSS. See the Tailwind documentation for setup instructions.
import tailwindPlugin from "@tailwindcss/bun";

await Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./out",
  plugins: [tailwindPlugin()],
});

Performance

Bun’s CSS bundler is extremely fast:
  • Written in Zig for maximum performance
  • Parallel processing of CSS files
  • Efficient memory usage
  • Fast source map generation

Limitations

  • No PostCSS support yet (use plugins as an alternative)
  • No SASS/SCSS support (but nesting is supported natively)
  • CSS layers are preserved but not optimized

Build docs developers (and LLMs) love