Skip to main content
Bun has a built-in CSS bundler that handles @import statements, url() references, modern syntax lowering, vendor prefixing, and CSS Modules. It is a direct Rust→Zig port of LightningCSS, with a bundling approach inspired by esbuild.

Importing CSS

Import CSS files from JavaScript or TypeScript:
app.tsx
import "./reset.css";
import "./styles.css";
During bundling, all imported CSS is combined into a single .css file in outdir. The @import statements and url() references in your CSS are processed and rebased automatically. CSS can also be imported from HTML files (via <link rel="stylesheet">), which is the most common pattern when using the fullstack dev server.

Default browser targets

Bun’s CSS bundler targets the following browsers by default:
  • Chrome 87+
  • Firefox 78+
  • Safari 14+
  • Edge 88+
Modern CSS that isn’t supported in these targets is automatically lowered to compatible equivalents.

Syntax lowering

Nesting

CSS nesting is automatically flattened for browser compatibility:
.card {
  background: white;
  border-radius: 4px;

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

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

Custom properties (CSS variables)

CSS variables work as-is — no special configuration needed. Bun’s bundler does not transform them, since they are widely supported and their runtime behavior cannot be replicated at build time.
:root {
  --primary: #2563eb;
  --spacing-md: 1rem;
}

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

Modern color functions

Bun evaluates color-mix(), relative color syntax, LAB/LCH/OKLAB/OKLCH, and HWB colors at build time when all values are constants, and generates browser-compatible fallbacks.
.button {
  background-color: color-mix(in srgb, blue 30%, red);
  color: lab(55% 78 35);
}

.theme {
  --accent: oklch(from purple calc(l + 15%) c h);
}

light-dark() function

The light-dark() function is automatically lowered to a @media (prefers-color-scheme) equivalent:
:root {
  color-scheme: light dark;
}

.card {
  background-color: light-dark(#ffffff, #121212);
  color: light-dark(#333333, #eeeeee);
}

Media query ranges

Range syntax in media queries is converted to min-width/max-width equivalents:
@media (width >= 768px) {
  .container { max-width: 720px; }
}

@media (768px <= width <= 1199px) {
  .sidebar { display: flex; }
}

Logical properties

Logical CSS properties are compiled to their physical equivalents with directional fallbacks:
/* Input */
.component {
  margin-inline-start: 1rem;
  padding-block: 1rem 2rem;
  inline-size: 80%;
}

/* Output */
.component:dir(ltr) {
  margin-left: 1rem;
  padding-top: 1rem;
  padding-bottom: 2rem;
  width: 80%;
}

.component:dir(rtl) {
  margin-right: 1rem;
  padding-top: 1rem;
  padding-bottom: 2rem;
  width: 80%;
}

Math functions

CSS math functions like round(), mod(), sin(), cos(), pow(), and sqrt() are evaluated at build time when all operands are constants:
/* Input */
.element {
  padding: round(14.8px, 5px);
  transform: rotate(calc(sin(45deg) * 50deg));
}

/* Output */
.element {
  padding: 15px;
  transform: rotate(35.36deg);
}

Modern shorthand properties

CSS shorthands like place-items, place-content, overflow: hidden auto, and display: inline flex are expanded to longhands for older browsers:
/* Input */
.container {
  place-items: center start;
  overflow: hidden auto;
}

/* Output */
.container {
  align-items: center;
  justify-items: start;
  overflow-x: hidden;
  overflow-y: auto;
}

system-ui font

system-ui is expanded to a comprehensive cross-platform font stack:
/* Input */
body { font-family: system-ui, sans-serif; }

/* Output */
body {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}

CSS Modules

CSS Modules scope class names to the file to prevent collisions. Create a file with the .module.css extension:
styles.module.css
.button {
  color: red;
}

.container {
  padding: 1rem;
}
Import it in TypeScript/TSX:
app.tsx
import styles from "./styles.module.css";

export function App() {
  return (
    <div className={styles.container}>
      <button className={styles.button}>Click me</button>
    </div>
  );
}
The imported styles object maps original class names to scoped unique identifiers:
console.log(styles.button);    // => "button_abc123"
console.log(styles.container); // => "container_abc123"

Composition

CSS Modules support composes to reuse styles across classes:
styles.module.css
.background {
  background-color: blue;
}

.button {
  composes: background;
  color: red;
}
You can also compose from a separate CSS module file:
button.module.css
.button {
  composes: background from "./background.module.css";
  color: red;
}
Composition rules: the composes property must come before any regular CSS properties, and can only be used on simple class selectors.
When composing from separate files, avoid conflicting properties between the composed classes — the CSS Modules spec leaves the order of conflicting properties undefined.

PostCSS not required

Bun’s CSS bundler handles the features most commonly added via PostCSS plugins:
FeaturePostCSS pluginBun
Vendor prefixesautoprefixerBuilt-in
CSS nestingpostcss-nestingBuilt-in
Modern colorspostcss-color-*Built-in
Media query rangespostcss-media-minmaxBuilt-in
Logical propertiespostcss-logicalBuilt-in
CSS Modulespostcss-modulesBuilt-in
PostCSS is not run by Bun’s bundler. If you rely on PostCSS plugins not covered by Bun’s built-in transforms, you can add a plugin via the bundler plugin API or use TailwindCSS’s bun-plugin-tailwind.

TailwindCSS

Use TailwindCSS with the official Bun plugin:
bun add tailwindcss bun-plugin-tailwind
bunfig.toml
[serve.static]
plugins = ["bun-plugin-tailwind"]
index.html
<link rel="stylesheet" href="tailwindcss" />
Or import it in CSS:
styles.css
@import "tailwindcss";

.custom {
  @apply bg-blue-500 text-white;
}

Build docs developers (and LLMs) love