1012 lines
25 KiB
Plaintext
1012 lines
25 KiB
Plaintext
---
|
|||
|
|
title: CSS
|
||
|
|
description: Bun's bundler has built-in support for CSS with modern features
|
||
|
|
---
|
||
|
|
|
||
|
|
Bun's bundler has built-in support for CSS with the following features:
|
||
|
|
|
||
|
|
- Transpiling modern/future features to work on all browsers (including vendor prefixing)
|
||
|
|
- Minification
|
||
|
|
- CSS Modules
|
||
|
|
- Tailwind (through a native bundler plugin)
|
||
|
|
|
||
|
|
## Transpiling
|
||
|
|
|
||
|
|
Transpiling and vendor prefixing are enabled by default, so you can use modern and future CSS features without worrying about browser compatibility.
|
||
|
|
|
||
|
|
Bun's CSS parser and bundler is a direct port of LightningCSS, with a bundling approach inspired by esbuild. The transpiler converts modern CSS syntax into backwards-compatible equivalents that work across browsers.
|
||
|
|
|
||
|
|
<Note>Thanks to the authors of LightningCSS and esbuild for their work.</Note>
|
||
|
|
|
||
|
|
## Browser Compatibility
|
||
|
|
|
||
|
|
By default, Bun's CSS bundler targets the following browsers:
|
||
|
|
|
||
|
|
- Edge 80+
|
||
|
|
- Firefox 78+
|
||
|
|
- Chrome 80+
|
||
|
|
- Safari 14+
|
||
|
|
- Opera 67+
|
||
|
|
|
||
|
|
## Syntax Lowering
|
||
|
|
|
||
|
|
### Nesting
|
||
|
|
|
||
|
|
With CSS Nesting, you write child styles directly inside their parent blocks instead of repeating parent selectors across your CSS file.
|
||
|
|
|
||
|
|
```scss title="styles.css" icon="file-code"
|
||
|
|
/* With 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 that works in all browsers:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* 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 inside selectors:
|
||
|
|
|
||
|
|
```scss title="styles.css" icon="file-code"
|
||
|
|
.responsive-element {
|
||
|
|
display: block;
|
||
|
|
|
||
|
|
@media (min-width: 768px) {
|
||
|
|
display: flex;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
This compiles to:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.responsive-element {
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (min-width: 768px) {
|
||
|
|
.responsive-element {
|
||
|
|
display: flex;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Color mix
|
||
|
|
|
||
|
|
The `color-mix()` function blends two colors at a given ratio in a chosen color space. Use it to create color variations without calculating the resulting values yourself.
|
||
|
|
|
||
|
|
```scss title="styles.css" icon="file-code"
|
||
|
|
.button {
|
||
|
|
/* Mix blue and red in the RGB color space with a 30/70 proportion */
|
||
|
|
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:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.button {
|
||
|
|
/* Computed to the exact resulting color */
|
||
|
|
background-color: #b31a1a;
|
||
|
|
}
|
||
|
|
|
||
|
|
.button:hover {
|
||
|
|
background-color: #c54747;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Relative colors
|
||
|
|
|
||
|
|
Relative color syntax modifies individual components of an existing color. Adjust attributes like lightness, saturation, or individual channels without recalculating the entire color.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.theme-color {
|
||
|
|
/* Start with a base color and increase 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 computes these relative color modifications at build time (when not using CSS variables) and generates static color values for browser compatibility:
|
||
|
|
|
||
|
|
```css
|
||
|
|
.theme-color {
|
||
|
|
--accent: lch(69.32% 58.34 328.37);
|
||
|
|
--subtle-blue: oklch(60.92% 0.112 240.01);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Use it for theme generation, accessible color variants, or color scales derived from a base color instead of hard-coding each value.
|
||
|
|
|
||
|
|
### LAB colors
|
||
|
|
|
||
|
|
Modern CSS supports the perceptually uniform color spaces LAB, LCH, OKLAB, and OKLCH, which can represent colors outside the standard RGB gamut.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.vibrant-element {
|
||
|
|
/* A vibrant red that exceeds sRGB gamut boundaries */
|
||
|
|
color: lab(55% 78 35);
|
||
|
|
|
||
|
|
/* A 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 converts these color formats to backwards-compatible alternatives for browsers that don't support them:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.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 preserved for browsers that support it */
|
||
|
|
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));
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Color function
|
||
|
|
|
||
|
|
The `color()` function specifies colors in predefined color spaces beyond traditional RGB, giving you access to wider color gamuts.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.vivid-element {
|
||
|
|
/* Using the Display P3 color space for wider gamut colors */
|
||
|
|
color: color(display-p3 1 0.1 0.3);
|
||
|
|
|
||
|
|
/* Using A98 RGB color space */
|
||
|
|
background-color: color(a98-rgb 0.44 0.5 0.37);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For browsers that don't support these color spaces, Bun's CSS bundler adds RGB fallbacks:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.vivid-element {
|
||
|
|
/* RGB fallback first for maximum compatibility */
|
||
|
|
color: #fa1a4c;
|
||
|
|
/* Keep original for browsers that support it */
|
||
|
|
color: color(display-p3 1 0.1 0.3);
|
||
|
|
|
||
|
|
background-color: #6a805d;
|
||
|
|
background-color: color(a98-rgb 0.44 0.5 0.37);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### HWB colors
|
||
|
|
|
||
|
|
The HWB (Hue, Whiteness, Blackness) color model expresses colors based on how much white or black is mixed with a pure hue. This makes tints and shades more direct to create than with RGB or HSL values.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.easy-theming {
|
||
|
|
/* Pure cyan with no white or black added */
|
||
|
|
--primary: hwb(180 0% 0%);
|
||
|
|
|
||
|
|
/* Same hue, but with 20% white added (tint) */
|
||
|
|
--primary-light: hwb(180 20% 0%);
|
||
|
|
|
||
|
|
/* Same hue, but with 30% black added (shade) */
|
||
|
|
--primary-dark: hwb(180 0% 30%);
|
||
|
|
|
||
|
|
/* Muted version with both white and black added */
|
||
|
|
--primary-muted: hwb(180 30% 20%);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Bun's CSS bundler converts HWB colors to RGB for compatibility with all browsers:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.easy-theming {
|
||
|
|
--primary: #00ffff;
|
||
|
|
--primary-light: #33ffff;
|
||
|
|
--primary-dark: #00b3b3;
|
||
|
|
--primary-muted: #339999;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Color notation
|
||
|
|
|
||
|
|
Modern CSS supports space-separated RGB and HSL values (no commas) and hex colors with an alpha channel.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.modern-styling {
|
||
|
|
/* Space-separated RGB notation (no commas) */
|
||
|
|
color: rgb(50 100 200);
|
||
|
|
|
||
|
|
/* Space-separated RGB with alpha */
|
||
|
|
border-color: rgba(100 50 200 / 75%);
|
||
|
|
|
||
|
|
/* Hex with alpha channel (8 digits) */
|
||
|
|
background-color: #00aaff80;
|
||
|
|
|
||
|
|
/* HSL with simplified notation */
|
||
|
|
box-shadow: 0 5px 10px hsl(200 50% 30% / 40%);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Bun's CSS bundler converts these formats for older browsers:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.modern-styling {
|
||
|
|
/* Converted to comma format for older browsers */
|
||
|
|
color: rgb(50, 100, 200);
|
||
|
|
|
||
|
|
/* Alpha channels handled appropriately */
|
||
|
|
border-color: rgba(100, 50, 200, 0.75);
|
||
|
|
|
||
|
|
/* Hex+alpha converted to rgba when needed */
|
||
|
|
background-color: rgba(0, 170, 255, 0.5);
|
||
|
|
|
||
|
|
box-shadow: 0 5px 10px rgba(38, 115, 153, 0.4);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### light-dark() color function
|
||
|
|
|
||
|
|
The `light-dark()` function takes two colors and applies one based on the current color scheme, so styles respect the user's system preference without media queries.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
:root {
|
||
|
|
/* Define color scheme support */
|
||
|
|
color-scheme: light dark;
|
||
|
|
}
|
||
|
|
|
||
|
|
.themed-component {
|
||
|
|
/* Automatically picks the right color based on system preference */
|
||
|
|
background-color: light-dark(#ffffff, #121212);
|
||
|
|
color: light-dark(#333333, #eeeeee);
|
||
|
|
border-color: light-dark(#dddddd, #555555);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Override system preference when needed */
|
||
|
|
.light-theme {
|
||
|
|
color-scheme: light;
|
||
|
|
}
|
||
|
|
|
||
|
|
.dark-theme {
|
||
|
|
color-scheme: dark;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For browsers that don't support `light-dark()`, Bun's CSS bundler converts it to CSS variables with fallbacks:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
:root {
|
||
|
|
--buncss-light: initial;
|
||
|
|
--buncss-dark: ;
|
||
|
|
color-scheme: light dark;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (prefers-color-scheme: dark) {
|
||
|
|
:root {
|
||
|
|
--buncss-light: ;
|
||
|
|
--buncss-dark: initial;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.light-theme {
|
||
|
|
--buncss-light: initial;
|
||
|
|
--buncss-dark: ;
|
||
|
|
color-scheme: light;
|
||
|
|
}
|
||
|
|
|
||
|
|
.dark-theme {
|
||
|
|
--buncss-light: ;
|
||
|
|
--buncss-dark: initial;
|
||
|
|
color-scheme: dark;
|
||
|
|
}
|
||
|
|
|
||
|
|
.themed-component {
|
||
|
|
background-color: var(--buncss-light, #ffffff) var(--buncss-dark, #121212);
|
||
|
|
color: var(--buncss-light, #333333) var(--buncss-dark, #eeeeee);
|
||
|
|
border-color: var(--buncss-light, #dddddd) var(--buncss-dark, #555555);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Logical properties
|
||
|
|
|
||
|
|
CSS logical properties define layout, spacing, and sizing relative to the document's writing mode and text direction rather than physical screen directions, so layouts adapt to different writing systems.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.multilingual-component {
|
||
|
|
/* Margin that adapts to writing direction */
|
||
|
|
margin-inline-start: 1rem;
|
||
|
|
|
||
|
|
/* Padding that makes sense regardless of text direction */
|
||
|
|
padding-block: 1rem 2rem;
|
||
|
|
|
||
|
|
/* Border radius for the starting corner at the top */
|
||
|
|
border-start-start-radius: 4px;
|
||
|
|
|
||
|
|
/* Size that respects the writing mode */
|
||
|
|
inline-size: 80%;
|
||
|
|
block-size: auto;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For browsers that don't fully support logical properties, Bun's CSS bundler compiles them to physical properties for each text direction:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* For left-to-right languages */
|
||
|
|
.multilingual-component:dir(ltr) {
|
||
|
|
margin-left: 1rem;
|
||
|
|
padding-top: 1rem;
|
||
|
|
padding-bottom: 2rem;
|
||
|
|
border-top-left-radius: 4px;
|
||
|
|
width: 80%;
|
||
|
|
height: auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* For right-to-left languages */
|
||
|
|
.multilingual-component:dir(rtl) {
|
||
|
|
margin-right: 1rem;
|
||
|
|
padding-top: 1rem;
|
||
|
|
padding-bottom: 2rem;
|
||
|
|
border-top-right-radius: 4px;
|
||
|
|
width: 80%;
|
||
|
|
height: auto;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
If the `:dir()` selector isn't supported, Bun generates additional fallbacks.
|
||
|
|
|
||
|
|
### :dir() selector
|
||
|
|
|
||
|
|
The `:dir()` pseudo-class styles elements based on their text direction (RTL or LTR), as determined by the document or explicit direction attributes. Use it to write direction-aware styles without JavaScript.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Apply different styles based on text direction */
|
||
|
|
.nav-arrow:dir(ltr) {
|
||
|
|
transform: rotate(0deg);
|
||
|
|
}
|
||
|
|
|
||
|
|
.nav-arrow:dir(rtl) {
|
||
|
|
transform: rotate(180deg);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Position elements based on text flow */
|
||
|
|
.sidebar:dir(ltr) {
|
||
|
|
border-right: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
|
||
|
|
.sidebar:dir(rtl) {
|
||
|
|
border-left: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For browsers that don't support the `:dir()` selector, Bun's CSS bundler converts it to the more widely supported `:lang()` selector with appropriate language mappings:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Converted to use language-based selectors as fallback */
|
||
|
|
.nav-arrow:lang(en, fr, de, es, it, pt, nl) {
|
||
|
|
transform: rotate(0deg);
|
||
|
|
}
|
||
|
|
|
||
|
|
.nav-arrow:lang(ar, he, fa, ur) {
|
||
|
|
transform: rotate(180deg);
|
||
|
|
}
|
||
|
|
|
||
|
|
.sidebar:lang(en, fr, de, es, it, pt, nl) {
|
||
|
|
border-right: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
|
||
|
|
.sidebar:lang(ar, he, fa, ur) {
|
||
|
|
border-left: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
If multiple arguments to `:lang()` aren't supported, Bun generates further fallbacks.
|
||
|
|
|
||
|
|
### :lang() selector
|
||
|
|
|
||
|
|
The `:lang()` pseudo-class targets elements based on their language. To group rules for related languages, pass multiple language codes to a single `:lang()`.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Typography adjustments for CJK languages */
|
||
|
|
:lang(zh, ja, ko) {
|
||
|
|
line-height: 1.8;
|
||
|
|
font-size: 1.05em;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Different quote styles by language group */
|
||
|
|
blockquote:lang(fr, it, es, pt) {
|
||
|
|
font-style: italic;
|
||
|
|
}
|
||
|
|
|
||
|
|
blockquote:lang(de, nl, da, sv) {
|
||
|
|
font-weight: 500;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For browsers that don't support multiple arguments in the `:lang()` selector, Bun's CSS bundler converts this syntax to the `:is()` selector with the same behavior:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Multiple languages grouped with :is() for better browser support */
|
||
|
|
:is(:lang(zh), :lang(ja), :lang(ko)) {
|
||
|
|
line-height: 1.8;
|
||
|
|
font-size: 1.05em;
|
||
|
|
}
|
||
|
|
|
||
|
|
blockquote:is(:lang(fr), :lang(it), :lang(es), :lang(pt)) {
|
||
|
|
font-style: italic;
|
||
|
|
}
|
||
|
|
|
||
|
|
blockquote:is(:lang(de), :lang(nl), :lang(da), :lang(sv)) {
|
||
|
|
font-weight: 500;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
If needed, Bun can generate additional fallbacks for `:is()` as well.
|
||
|
|
|
||
|
|
### :is() selector
|
||
|
|
|
||
|
|
The `:is()` pseudo-class function (formerly `:matches()`) takes a selector list and matches if any selector in the list matches.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Instead of writing these separately */
|
||
|
|
/*
|
||
|
|
.article h1,
|
||
|
|
.article h2,
|
||
|
|
.article h3 {
|
||
|
|
margin-top: 1.5em;
|
||
|
|
}
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* You can write this */
|
||
|
|
.article :is(h1, h2, h3) {
|
||
|
|
margin-top: 1.5em;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Complex example with multiple groups */
|
||
|
|
:is(header, main, footer) :is(h1, h2, .title) {
|
||
|
|
font-family: "Heading Font", sans-serif;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For browsers that don't support `:is()`, Bun's CSS bundler provides fallbacks using vendor-prefixed alternatives:
|
||
|
|
|
||
|
|
```css
|
||
|
|
/* Fallback using -webkit-any */
|
||
|
|
.article :-webkit-any(h1, h2, h3) {
|
||
|
|
margin-top: 1.5em;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Fallback using -moz-any */
|
||
|
|
.article :-moz-any(h1, h2, h3) {
|
||
|
|
margin-top: 1.5em;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Original preserved for modern browsers */
|
||
|
|
.article :is(h1, h2, h3) {
|
||
|
|
margin-top: 1.5em;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Complex example with fallbacks */
|
||
|
|
:-webkit-any(header, main, footer) :-webkit-any(h1, h2, .title) {
|
||
|
|
font-family: "Heading Font", sans-serif;
|
||
|
|
}
|
||
|
|
|
||
|
|
:-moz-any(header, main, footer) :-moz-any(h1, h2, .title) {
|
||
|
|
font-family: "Heading Font", sans-serif;
|
||
|
|
}
|
||
|
|
|
||
|
|
:is(header, main, footer) :is(h1, h2, .title) {
|
||
|
|
font-family: "Heading Font", sans-serif;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
<Warning>
|
||
|
|
The vendor-prefixed versions have limitations compared to the standardized `:is()` selector, particularly with complex
|
||
|
|
selectors. Bun only uses the prefixed versions when they work correctly.
|
||
|
|
</Warning>
|
||
|
|
|
||
|
|
### :not() selector
|
||
|
|
|
||
|
|
The `:not()` pseudo-class excludes elements that match a selector. The modern version accepts multiple arguments to exclude several patterns with one `:not()`.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Select all buttons except primary and secondary variants */
|
||
|
|
button:not(.primary, .secondary) {
|
||
|
|
background-color: #f5f5f5;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Apply styles to all headings except those inside sidebars or footers */
|
||
|
|
h2:not(.sidebar *, footer *) {
|
||
|
|
margin-top: 2em;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For browsers that don't support multiple arguments in `:not()`, Bun's CSS bundler converts this syntax to a more compatible form with the same behavior:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Converted to use :not with :is() for compatibility */
|
||
|
|
button:not(:is(.primary, .secondary)) {
|
||
|
|
background-color: #f5f5f5;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
|
||
|
|
h2:not(:is(.sidebar *, footer *)) {
|
||
|
|
margin-top: 2em;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
And if `:is()` isn't supported, Bun can generate further fallbacks:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Even more fallbacks for maximum compatibility */
|
||
|
|
button:not(:-webkit-any(.primary, .secondary)) {
|
||
|
|
background-color: #f5f5f5;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
|
||
|
|
button:not(:-moz-any(.primary, .secondary)) {
|
||
|
|
background-color: #f5f5f5;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
|
||
|
|
button:not(:is(.primary, .secondary)) {
|
||
|
|
background-color: #f5f5f5;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
The converted selectors keep the specificity and behavior of the original.
|
||
|
|
|
||
|
|
### Math functions
|
||
|
|
|
||
|
|
CSS includes the following math functions:
|
||
|
|
|
||
|
|
- Standard math functions: `round()`, `mod()`, `rem()`, `abs()`, `sign()`
|
||
|
|
- Trigonometric functions: `sin()`, `cos()`, `tan()`, `asin()`, `acos()`, `atan()`, `atan2()`
|
||
|
|
- Exponential functions: `pow()`, `sqrt()`, `exp()`, `log()`, `hypot()`
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.dynamic-sizing {
|
||
|
|
/* Clamp a value between minimum and maximum */
|
||
|
|
width: clamp(200px, 50%, 800px);
|
||
|
|
|
||
|
|
/* Round to the nearest multiple */
|
||
|
|
padding: round(14.8px, 5px);
|
||
|
|
|
||
|
|
/* Trigonometry for animations or layouts */
|
||
|
|
transform: rotate(calc(sin(45deg) * 50deg));
|
||
|
|
|
||
|
|
/* Complex math with multiple functions */
|
||
|
|
--scale-factor: pow(1.25, 3);
|
||
|
|
font-size: calc(16px * var(--scale-factor));
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Bun's CSS bundler evaluates these expressions at build time when all values are known constants (not variables):
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.dynamic-sizing {
|
||
|
|
width: clamp(200px, 50%, 800px);
|
||
|
|
padding: 15px;
|
||
|
|
transform: rotate(35.36deg);
|
||
|
|
--scale-factor: 1.953125;
|
||
|
|
font-size: calc(16px * var(--scale-factor));
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Media query ranges
|
||
|
|
|
||
|
|
Media query range syntax expresses breakpoints with comparison operators (`<`, `>`, `<=`, `>=`) instead of the more verbose `min-` and `max-` prefixes.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Modern syntax with comparison operators */
|
||
|
|
@media (width >= 768px) {
|
||
|
|
.container {
|
||
|
|
max-width: 720px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Inclusive range using <= and >= */
|
||
|
|
@media (768px <= width <= 1199px) {
|
||
|
|
.sidebar {
|
||
|
|
display: flex;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Exclusive range using < and > */
|
||
|
|
@media (width > 320px) and (width < 768px) {
|
||
|
|
.mobile-only {
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Bun's CSS bundler converts range queries to traditional media query syntax for compatibility with all browsers:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Converted to traditional min/max syntax */
|
||
|
|
@media (min-width: 768px) {
|
||
|
|
.container {
|
||
|
|
max-width: 720px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (min-width: 768px) and (max-width: 1199px) {
|
||
|
|
.sidebar {
|
||
|
|
display: flex;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (min-width: 321px) and (max-width: 767px) {
|
||
|
|
.mobile-only {
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Shorthands
|
||
|
|
|
||
|
|
CSS has introduced several shorthand properties that combine multiple longhand properties.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
/* Alignment shorthands */
|
||
|
|
.flex-container {
|
||
|
|
/* Shorthand for align-items and justify-items */
|
||
|
|
place-items: center start;
|
||
|
|
|
||
|
|
/* Shorthand for align-content and justify-content */
|
||
|
|
place-content: space-between center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.grid-item {
|
||
|
|
/* Shorthand for align-self and justify-self */
|
||
|
|
place-self: end center;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Two-value overflow */
|
||
|
|
.content-box {
|
||
|
|
/* First value for horizontal, second for vertical */
|
||
|
|
overflow: hidden auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Enhanced text-decoration */
|
||
|
|
.fancy-link {
|
||
|
|
/* Combines multiple text decoration properties */
|
||
|
|
text-decoration: underline dotted blue 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Two-value display syntax */
|
||
|
|
.component {
|
||
|
|
/* Outer display type + inner display type */
|
||
|
|
display: inline flex;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For browsers that don't support these shorthands, Bun converts them to their component longhand properties:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.flex-container {
|
||
|
|
/* Expanded alignment properties */
|
||
|
|
align-items: center;
|
||
|
|
justify-items: start;
|
||
|
|
|
||
|
|
align-content: space-between;
|
||
|
|
justify-content: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.grid-item {
|
||
|
|
align-self: end;
|
||
|
|
justify-self: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.content-box {
|
||
|
|
/* Separate overflow properties */
|
||
|
|
overflow-x: hidden;
|
||
|
|
overflow-y: auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
.fancy-link {
|
||
|
|
/* Individual text decoration properties */
|
||
|
|
text-decoration-line: underline;
|
||
|
|
text-decoration-style: dotted;
|
||
|
|
text-decoration-color: blue;
|
||
|
|
text-decoration-thickness: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.component {
|
||
|
|
/* Single value display */
|
||
|
|
display: inline-flex;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Double position gradients
|
||
|
|
|
||
|
|
Double position gradient syntax specifies the same color at two adjacent positions to create a hard color stop: a sharp transition instead of a smooth fade. Use it for stripes, color bands, and other multi-color designs.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.striped-background {
|
||
|
|
/* Creates a sharp transition from green to red at 30%-40% */
|
||
|
|
background: linear-gradient(
|
||
|
|
to right,
|
||
|
|
yellow 0%,
|
||
|
|
green 20%,
|
||
|
|
green 30%,
|
||
|
|
red 30%,
|
||
|
|
/* Double position creates hard stop */ red 70%,
|
||
|
|
blue 70%,
|
||
|
|
blue 100%
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
.progress-bar {
|
||
|
|
/* Creates distinct color sections */
|
||
|
|
background: linear-gradient(
|
||
|
|
to right,
|
||
|
|
#4caf50 0% 25%,
|
||
|
|
/* Green from 0% to 25% */ #ffc107 25% 50%,
|
||
|
|
/* Yellow from 25% to 50% */ #2196f3 50% 75%,
|
||
|
|
/* Blue from 50% to 75% */ #9c27b0 75% 100% /* Purple from 75% to 100% */
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For browsers that don't support this syntax, Bun's CSS bundler converts it to the traditional format by duplicating color stops:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.striped-background {
|
||
|
|
background: linear-gradient(
|
||
|
|
to right,
|
||
|
|
yellow 0%,
|
||
|
|
green 20%,
|
||
|
|
green 30%,
|
||
|
|
red 30%,
|
||
|
|
/* Split into two color stops */ red 70%,
|
||
|
|
blue 70%,
|
||
|
|
blue 100%
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
.progress-bar {
|
||
|
|
background: linear-gradient(
|
||
|
|
to right,
|
||
|
|
#4caf50 0%,
|
||
|
|
#4caf50 25%,
|
||
|
|
/* Two stops for green section */ #ffc107 25%,
|
||
|
|
#ffc107 50%,
|
||
|
|
/* Two stops for yellow section */ #2196f3 50%,
|
||
|
|
#2196f3 75%,
|
||
|
|
/* Two stops for blue section */ #9c27b0 75%,
|
||
|
|
#9c27b0 100% /* Two stops for purple section */
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### system-ui font
|
||
|
|
|
||
|
|
The `system-ui` generic font family uses the device's native UI font.
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.native-interface {
|
||
|
|
/* Use the system's default UI font */
|
||
|
|
font-family: system-ui;
|
||
|
|
}
|
||
|
|
|
||
|
|
.fallback-aware {
|
||
|
|
/* System UI font with explicit fallbacks */
|
||
|
|
font-family: system-ui, sans-serif;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
For browsers that don't support `system-ui`, Bun's CSS bundler expands it to a cross-platform font stack:
|
||
|
|
|
||
|
|
```css title="styles.css" icon="file-code"
|
||
|
|
.native-interface {
|
||
|
|
/* Expanded to support all major platforms */
|
||
|
|
font-family:
|
||
|
|
system-ui,
|
||
|
|
-apple-system,
|
||
|
|
BlinkMacSystemFont,
|
||
|
|
"Segoe UI",
|
||
|
|
Roboto,
|
||
|
|
"Noto Sans",
|
||
|
|
Ubuntu,
|
||
|
|
Cantarell,
|
||
|
|
"Helvetica Neue";
|
||
|
|
}
|
||
|
|
|
||
|
|
.fallback-aware {
|
||
|
|
/* Preserves the original fallback after the expanded stack */
|
||
|
|
font-family:
|
||
|
|
system-ui,
|
||
|
|
-apple-system,
|
||
|
|
BlinkMacSystemFont,
|
||
|
|
"Segoe UI",
|
||
|
|
Roboto,
|
||
|
|
"Noto Sans",
|
||
|
|
Ubuntu,
|
||
|
|
Cantarell,
|
||
|
|
"Helvetica Neue",
|
||
|
|
sans-serif;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
The expanded stack includes system fonts for macOS/iOS, Windows, Android, and Linux, plus fallbacks for older browsers.
|
||
|
|
|
||
|
|
## CSS Modules
|
||
|
|
|
||
|
|
Bun's bundler also supports CSS modules, with the following features:
|
||
|
|
|
||
|
|
- Detecting CSS module files (`.module.css`) with no configuration
|
||
|
|
- Composition (`composes` property)
|
||
|
|
- Importing CSS modules into JSX/TSX
|
||
|
|
- Warnings/errors for invalid usages of CSS modules
|
||
|
|
|
||
|
|
A CSS module is a CSS file (with the `.module.css` extension) where all class names and animations are scoped to the file. This helps you avoid class name collisions, as CSS declarations are globally scoped by default.
|
||
|
|
|
||
|
|
Bun's bundler transforms locally scoped class names into unique identifiers.
|
||
|
|
|
||
|
|
### Getting started
|
||
|
|
|
||
|
|
Create a CSS file with the `.module.css` extension:
|
||
|
|
|
||
|
|
```css title="styles.module.css" icon="file-code"
|
||
|
|
.button {
|
||
|
|
color: red;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
```css title="other-styles.module.css" icon="file-code"
|
||
|
|
.button {
|
||
|
|
color: blue;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
You can then import this file, for example into a TSX file:
|
||
|
|
|
||
|
|
```tsx title="app.tsx" icon="/icons/typescript.svg"
|
||
|
|
import styles from "./styles.module.css";
|
||
|
|
import otherStyles from "./other-styles.module.css";
|
||
|
|
|
||
|
|
export default function App() {
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
<button className={styles.button}>Red button!</button>
|
||
|
|
<button className={otherStyles.button}>Blue button!</button>
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Importing a CSS module gives you an object that maps each class name to its unique identifier:
|
||
|
|
|
||
|
|
```ts title="app.tsx" icon="/icons/typescript.svg"
|
||
|
|
import styles from "./styles.module.css";
|
||
|
|
import otherStyles from "./other-styles.module.css";
|
||
|
|
|
||
|
|
console.log(styles);
|
||
|
|
console.log(otherStyles);
|
||
|
|
```
|
||
|
|
|
||
|
|
This outputs:
|
||
|
|
|
||
|
|
```ts title="app.tsx" icon="/icons/typescript.svg"
|
||
|
|
{
|
||
|
|
button: "button_123";
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
button: "button_456";
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
The class names are unique to each file, so they don't collide.
|
||
|
|
|
||
|
|
### Composition
|
||
|
|
|
||
|
|
CSS modules can compose class selectors together to reuse style rules across multiple classes.
|
||
|
|
|
||
|
|
For example:
|
||
|
|
|
||
|
|
```css title="styles.module.css" icon="file-code"
|
||
|
|
.button {
|
||
|
|
composes: background;
|
||
|
|
color: red;
|
||
|
|
}
|
||
|
|
|
||
|
|
.background {
|
||
|
|
background-color: blue;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
This is the same as writing:
|
||
|
|
|
||
|
|
```css title="styles.module.css" icon="file-code"
|
||
|
|
.button {
|
||
|
|
background-color: blue;
|
||
|
|
color: red;
|
||
|
|
}
|
||
|
|
|
||
|
|
.background {
|
||
|
|
background-color: blue;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Two rules apply when using `composes`:
|
||
|
|
|
||
|
|
<Info>
|
||
|
|
|
||
|
|
**Composition Rules:**
|
||
|
|
|
||
|
|
- A `composes` property must come before any regular CSS properties or declarations
|
||
|
|
- You can only use `composes` on a simple selector with a single class name
|
||
|
|
|
||
|
|
</Info>
|
||
|
|
|
||
|
|
```css title="styles.module.css" icon="file-code"
|
||
|
|
#button {
|
||
|
|
/* Invalid! `#button` is not a class selector */
|
||
|
|
composes: background;
|
||
|
|
}
|
||
|
|
|
||
|
|
.button,
|
||
|
|
.button-secondary {
|
||
|
|
/* Invalid! `.button, .button-secondary` is not a simple selector */
|
||
|
|
composes: background;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Composing from a separate CSS module file
|
||
|
|
|
||
|
|
You can also compose from a separate CSS module file:
|
||
|
|
|
||
|
|
```css title="background.module.css" icon="file-code"
|
||
|
|
.background {
|
||
|
|
background-color: blue;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
```css title="styles.module.css" icon="file-code"
|
||
|
|
.button {
|
||
|
|
composes: background from "./background.module.css";
|
||
|
|
color: red;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
<Warning>
|
||
|
|
When composing classes from separate files, make sure they do not contain the same properties.
|
||
|
|
|
||
|
|
The CSS module spec says that composing classes from separate files with conflicting properties is undefined behavior: the output may differ and be unreliable.
|
||
|
|
|
||
|
|
</Warning>
|