You are an expert in PostCSS, CSS processing pipelines, and modern CSS tooling.
PostCSS is a tool for transforming CSS with JavaScript plugins. Unlike preprocessors (Sass/Less), PostCSS:
# Core PostCSS
npm install postcss postcss-cli --save-dev
# Common plugins
npm install autoprefixer cssnano postcss-preset-env --save-dev
# Optional plugins
npm install postcss-import postcss-nested postcss-custom-media --save-dev
// postcss.config.js
module.exports = {
plugins: [
require('postcss-import'),
require('postcss-preset-env')({
stage: 2,
features: {
'nesting-rules': true,
'custom-media-queries': true,
'custom-properties': true,
},
}),
require('autoprefixer'),
require('cssnano')({
preset: ['default', {
discardComments: { removeAll: true },
}],
}),
],
};
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
],
},
],
},
};
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
css: {
postcss: './postcss.config.js',
},
});
// postcss.config.js (auto-detected)
module.exports = {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {},
},
};
Enables modern CSS features with polyfills:
// postcss.config.js
module.exports = {
plugins: [
require('postcss-preset-env')({
stage: 2, // Stage 2 features are stable
features: {
'nesting-rules': true,
'custom-media-queries': true,
'custom-selectors': true,
'gap-properties': true,
'logical-properties-and-values': true,
},
autoprefixer: { grid: true },
browsers: 'last 2 versions',
}),
],
};
Adds vendor prefixes automatically:
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')({
grid: 'autoplace', // Enable grid prefixes
flexbox: true,
}),
],
};
// Input CSS
.element {
display: flex;
user-select: none;
}
// Output CSS
.element {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
Minifies and optimizes CSS for production:
// postcss.config.js
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
plugins: [
require('autoprefixer'),
isProduction && require('cssnano')({
preset: ['default', {
discardComments: { removeAll: true },
normalizeWhitespace: true,
minifyFontValues: true,
minifyGradients: true,
reduceIdents: false, // Preserve animation names
mergeRules: true,
mergeLonghand: true,
}],
}),
].filter(Boolean),
};
Inline @import statements:
// postcss.config.js
module.exports = {
plugins: [
require('postcss-import')({
path: ['src/styles'],
}),
],
};
/* main.css */
@import 'variables.css';
@import 'base.css';
@import 'components/buttons.css';
@import 'components/cards.css';
Enables Sass-like nesting:
// postcss.config.js
module.exports = {
plugins: [
require('postcss-nested'),
],
};
/* Input */
.card {
background: white;
&__header {
padding: 16px;
}
&__body {
padding: 16px;
}
&:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
@media (min-width: 768px) {
display: flex;
}
}
/* Output */
.card {
background: white;
}
.card__header {
padding: 16px;
}
.card__body {
padding: 16px;
}
.card:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
@media (min-width: 768px) {
.card {
display: flex;
}
}
/* Modern browsers support native nesting */
.card {
background: white;
border-radius: 8px;
& .card-header {
padding: 1rem;
border-bottom: 1px solid #eee;
}
& .card-body {
padding: 1rem;
}
&:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
@media (min-width: 768px) {
display: flex;
}
}
:root {
/* Colors */
--color-primary: #3498db;
--color-primary-light: color-mix(in srgb, var(--color-primary), white 20%);
--color-primary-dark: color-mix(in srgb, var(--color-primary), black 20%);
--color-text: #333333;
--color-background: #ffffff;
--color-border: #e0e0e0;
/* Typography */
--font-family-base: 'Helvetica Neue', Arial, sans-serif;
--font-size-base: 1rem;
--line-height-base: 1.5;
/* Spacing */
--spacing-unit: 8px;
--spacing-sm: calc(var(--spacing-unit) * 1);
--spacing-md: calc(var(--spacing-unit) * 2);
--spacing-lg: calc(var(--spacing-unit) * 3);
--spacing-xl: calc(var(--spacing-unit) * 4);
/* Transitions */
--transition-base: 0.3s ease;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.button {
padding: var(--spacing-sm) var(--spacing-md);
background: var(--color-primary);
color: white;
transition: background var(--transition-base);
}
.button:hover {
background: var(--color-primary-dark);
}
/* Define custom media queries */
@custom-media --viewport-sm (min-width: 576px);
@custom-media --viewport-md (min-width: 768px);
@custom-media --viewport-lg (min-width: 992px);
@custom-media --viewport-xl (min-width: 1200px);
@custom-media --motion-ok (prefers-reduced-motion: no-preference);
@custom-media --dark-mode (prefers-color-scheme: dark);
/* Usage */
.element {
width: 100%;
}
@media (--viewport-md) {
.element {
width: 50%;
}
}
@media (--viewport-lg) {
.element {
width: 33.333%;
}
}
@media (--motion-ok) {
.element {
transition: transform 0.3s ease;
}
}
/* Define reusable selectors */
@custom-selector :--heading h1, h2, h3, h4, h5, h6;
@custom-selector :--button button, [type="button"], [type="submit"];
@custom-selector :--enter :hover, :focus;
/* Usage */
:--heading {
font-family: var(--font-family-heading);
line-height: 1.2;
margin-bottom: 1rem;
}
:--button {
cursor: pointer;
font-family: inherit;
}
.link:--enter {
color: var(--color-primary);
text-decoration: underline;
}
/* Use logical properties for internationalization */
.element {
/* Instead of margin-left/right */
margin-inline: auto;
/* Instead of padding-top/bottom */
padding-block: var(--spacing-md);
/* Instead of width/height */
inline-size: 100%;
block-size: auto;
/* Instead of border-left */
border-inline-start: 2px solid var(--color-primary);
/* Instead of text-align: left */
text-align: start;
}
/* Modern container queries */
.card-container {
container-type: inline-size;
container-name: card;
}
.card {
display: block;
}
@container card (min-width: 400px) {
.card {
display: flex;
}
}
@container card (min-width: 600px) {
.card {
gap: 2rem;
}
}
src/
├── styles/
│ ├── base/
│ │ ├── reset.css
│ │ ├── typography.css
│ │ └── base.css
│ ├── components/
│ │ ├── buttons.css
│ │ ├── cards.css
│ │ ├── forms.css
│ │ └── navigation.css
│ ├── layout/
│ │ ├── header.css
│ │ ├── footer.css
│ │ └── grid.css
│ ├── utilities/
│ │ ├── spacing.css
│ │ ├── colors.css
│ │ └── display.css
│ ├── variables.css
│ └── main.css
├── postcss.config.js
└── package.json
/* main.css */
/* Variables first */
@import 'variables.css';
/* Reset and base styles */
@import 'base/reset.css';
@import 'base/typography.css';
@import 'base/base.css';
/* Layout */
@import 'layout/grid.css';
@import 'layout/header.css';
@import 'layout/footer.css';
/* Components */
@import 'components/buttons.css';
@import 'components/cards.css';
@import 'components/forms.css';
@import 'components/navigation.css';
/* Utilities (last for specificity) */
@import 'utilities/spacing.css';
@import 'utilities/colors.css';
@import 'utilities/display.css';
// postcss.config.js
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
plugins: [
// Always run these
require('postcss-import'),
require('postcss-preset-env')({
stage: 2,
features: {
'nesting-rules': true,
'custom-media-queries': true,
},
}),
require('autoprefixer'),
// Production only
isProduction && require('cssnano')({
preset: ['default', {
discardComments: { removeAll: true },
reduceIdents: false,
zindex: false,
}],
}),
isProduction && require('@fullhuman/postcss-purgecss')({
content: [
'./src/**/*.html',
'./src/**/*.js',
'./src/**/*.jsx',
'./src/**/*.tsx',
],
safelist: {
standard: [/^is-/, /^has-/, /^js-/],
deep: [/modal/, /tooltip/],
},
}),
].filter(Boolean),
};
// package.json
{
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}
// Or .browserslistrc
> 1%
last 2 versions
not dead
not ie 11
require('postcss-custom-media')({
importFrom: [
{
customMedia: {
'--viewport-sm': '(min-width: 576px)',
'--viewport-md': '(min-width: 768px)',
'--viewport-lg': '(min-width: 992px)',
'--viewport-xl': '(min-width: 1200px)',
},
},
],
});
require('postcss-mixins')({
mixins: {
'flex-center': {
display: 'flex',
'align-items': 'center',
'justify-content': 'center',
},
'visually-hidden': {
position: 'absolute',
width: '1px',
height: '1px',
overflow: 'hidden',
clip: 'rect(0, 0, 0, 0)',
},
},
});
require('postcss-simple-vars')({
variables: {
'color-primary': '#3498db',
'spacing-unit': '8px',
},
});
module.exports = {
plugins: [
require('autoprefixer'),
require('cssnano'),
],
};
module.exports = {
plugins: [
require('postcss-import'),
require('postcss-preset-env')({ stage: 2 }),
require('autoprefixer'),
require('cssnano'),
],
};
module.exports = {
plugins: [
require('postcss-import'),
require('postcss-mixins'),
require('postcss-simple-vars'),
require('postcss-nested'),
require('autoprefixer'),
require('cssnano'),
],
};
module.exports = {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
},
};
// postcss.config.js
module.exports = {
map: process.env.NODE_ENV !== 'production'
? { inline: false }
: false,
plugins: [
// ...plugins
],
};
# CLI with verbose output
postcss src/main.css -o dist/main.css --verbose
If styles aren't working as expected:
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).