Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

📢 If you came here after seeing a warning in postcss-custom-properties or postcss-env-function.
The proposed changes and deprecations are not rolling out soon.

We want to do this right and create a way forward for as many people as possible.
To do so we need feedback and use cases.
Please let us know if we overlooked something.


see : #151


Context for the original discussion :

Context :

importFrom and exportTo options need to be removed from plugins and postcss-preset-env.

These options count as "side effects". They do something non-standard and make your code dependent on the plugin running.

postcss-preset-env by definition is a tool to not run plugins.

The most clear example is using importFrom to inject CSS custom property values from a JSON file.
If the plugin does not run, the values are not injected and your CSS will be broken.

Proposal :

The general idea is to create something that does not have any interaction or logic with other features or behaviours.

Only has three features :

  • insert a value for a given key
  • extract a key/value pair
  • transform the CSS so that it is just plain standard CSS

Caveats and issues

CSS would be syntactically correct, but would have invalid values in many contexts.
This places pressure on other plugins to handle these cases with more safe guards (try/catch).

This does not address the issue of execution order.
When inserting bits this is not really and issue, but it is when extracting data.

Some might want to extract pieces of CSS as written others might want to extract the final result.
And the final result is often not possible to create until the custom functions introduced here have been stripped.

For exportTo it makes much more sense to have one or more plugins dedicated to exporting a certain feature (custom properties, custom media,...). Still added a proposal that mirrors my thinking for importFrom.

importFrom

  • a new css function : insert()
  • using a custom prefix (-csstools-) to avoid collisions with current or future standard features or other plugins
  • plugin only accepts JSON (no .js, .mjs, .css or functions/promises)

We only accept JSON as this is the one type that everyone can format too and that works reliably in all node versions, OS's, and in both modules and commonjs contexts.

It should not be the plugins responsibility to accept a wide range of formats.

  • -csstools-insert(<custom-ident>)
  • :-csstools-insert(<custom-ident>)

Examples :

postcss([
  postcssInsertData(yourData)
])
{
  "--foo": "1px"
}

declaration values

.foo {
  left: -csstools-insert(--foo);
}

Becomes :

.foo {
  left: -csstools-insert(1px);
}

custom properties

:root {
  --your-left-value: -csstools-insert(--foo);
}

Becomes :

.foo {
  left: var(--your-left-value);
}

exportTo

  • a new css function : extract()

  • using a custom prefix (-csstools-) to avoid collisions with current or future standard features or other plugins

  • plugin only emits JSON through a callback.

  • -csstools-extract(<any-value> | [<any-value>], <custom-ident>)

  • :-csstools-extract(<any-value> | [<any-value>], <custom-ident>)

Examples :

postcss([
  postcssExtractData((cssData) => { console.log(cssData) })
])

declaration values

.foo {
  left: -csstools-extract(1px, foo);
  background: linear-gradient(-csstools-extract(#e66465, #9198e5, gradient-colors));
}

Becomes :

.foo {
  left: 1px;
  background: linear-gradient(#e66465, #9198e5);
}

Emitting :

{
  "foo": ["1px"]
  "gradient-colors": ["#e66465", "#9198e5"]
}

selectors

.foo:-csstools-extract(.baz, baz) {
  order: 1;
}

Becomes :

.foo.baz {
  left: 1px;
}

Emitting :

{
  "baz": [".baz"]
}

custom media

@custom-media --small-viewport (-csstools-extract((max-width: 30em), small-viewport));

Becomes :

@custom-media --small-viewport (max-width: 30em);

Emitting :

{
  "small-viewport": ["(max-width: 30em)"]
}

This entire approach is more cumbersome to write, but that can be improved with code snippets in editors.

Alternatives and replacements :

Custom Media

Custom Properties

Custom Selectors

You must be logged in to vote

Hi everyone 👋

We have written the code for the drop-in replacements but we decided against publishing and maintaining these ourselves.

You can read more about it here : #734

TL;DR; if anyone is willing to take over maintenance of the functionality behind importFrom/exportTo please let us (and each other) know.

The source code is available and anyone is free to take this and publish their own plugins based on that.


To summarize :

exportTo

For exportTo we advice everyone to switch over to @csstools/postcss-extract. We think it is a more powerful tool and it isn't limited to a small set of features, anything can be exported.

If you do depend on exportTo and migrating is absolutely impossibl…

Replies: 22 comments · 54 replies

Comment options

New proposal

see : #304


This proposal interprets importFrom and exportTo as mechanics to create an interface between CSS and X.

The assumption is that very few authors write CSS that is truly dynamic with data fed to importFrom as the source. Adding an extra custom property in data does not change the output of the CSS bundle without also writing CSS that uses this new value.

For the moment I consider exportTo redundant as it is just a different way to build the interface. Defined in CSS vs defined in config. I might be very wrong about this, and please tell me if I am. Knowing about as many use cases is critical in getting this right.


Config :

{
	// Enforce using `design-token(foo)` for each `color` property
	requiresDesignTokens: {
		properties: [
			'color'
		]
	},
	designTokens: {
		selectors: [
			{
				name: 'button',
				value: ':is(.button, button[type="submit"])',
				deprecated: false, // emit a warning when design token is used
			}
		],
		atSupports: [
			{
				name: 'cover',
				value: '(object-fit: cover)',
				deprecated: false,
			}
		],
		atMedia: [
			{
				name: 'medium',
				value: '(min-width: 768px)',
				deprecated: false
			}
		],
		values: [
			{
				name: 'my-color',
				value: '#f00',
				deprecated: false,
				allowedProperties: [], // only allowed on certain properties
				blockedProperties: [], // not allowed on certain properties
			}
		]
	}
}

This focuses heavily on giving authors tools to manage the shared interface between CSS and X.

It allows the creation of much more strict contracts for how design tokens must or must not be used.


Example :

.foo {
	color: design-token(my-color);
}

@media (design-token: medium) {
	.baz {
		color: green;
	}
}

@supports (design-token: cover) {
	.baz {
		color: green;
		object-fit: cover;
	}
}

:design-token(button) {
	color: green;
}

No new syntax is introduced and no existing syntax or keywords are overloaded.
This means that it should just work within the existing ecosystem (linters, parsers, ...).

Each case has a specific context and prefix to support auto complete and snippet triggers in editors.

You must be logged in to vote
2 replies
@wesleyboar
Comment options

Do I correctly Interpret the output code?

Output CSS
.foo {
	color: #f00;
}

@media (min-width: 768px) {
	.baz {
		color: green;
	}
}

@supports (object-fit: cover) {
	.baz {
		color: green;
		object-fit: cover;
	}
}

:is(.button, button[type="submit"]) {
	color: green;
}
Also, are these typos in your sample code?
  • color: design-token(color);color: design-token(my-color);
  • value: '(min-width: 768px)'value: 'min-width: 768px' — Otherwise, output may be ((min-width: 768px)).
  • value: '(object-fit: cover)'value: 'object-fit: cover — Otherwise, output may be ((object-fit: cover)).
@romainmenke
Comment options

romainmenke Mar 25, 2022
Maintainer Author

Yes that was the intended output at the time :)

  • color -> my-color was a typo
  • for the others, the plugin would not use a string find/replace method. It would be parser based and would not result in duplicate (())

You still need to add () in the value because this also needs to work : screen and (min-width: 768px)

Comment options

the exportTo function can be handy for devs building design systems from existing sites. Extracting css custom properties from :root to .json, so that they can be looped over/filtered as needed for display of various "token" level things.

This is maybe redundant for new projects or when using things like tailwind, but for older projects where styles are not originally defined in .js this ability is super helpful.

A helper script before building the design system (say, storybook)

const postcss = require('postcss');
const postcssCustomProperties = require('postcss-custom-properties');
const fs = require('fs');
const path = require('path');

fs.readFile(path.resolve(__dirname,'../components/tokens/_variables.css'), (err, css) => {
  postcss({
    plugins: [
      postcssCustomProperties({
        exportTo: path.resolve(__dirname, './cssVariables.json')
      })
    ]
  }).process(css, {
    from: path.resolve(__dirname, '../components/tokens/_variables.css'),
    to: '' // don't need a css file here, just want the .json output from above
  }).then(result => {
    console.log('Finished processing CSS variables from ' + result.opts.from + ' for Storybook!');
  })
})

then in Storybook (or whatever design system) you can easily do logic to pass different groups of variables to various templates

import customProperties from 'path/defined/in/exportTo/cssVariables.json'

const vars = customProperties["custom-properties"];

const colorVars = Object.keys(vars)
  .filter(key => key.includes('--color'))
  .reduce((obj, key) => {
    obj[key] = vars[key];
    return obj;
  }, {});

export const Colors = {
  args: {
    colorVars
  },
  ...
}
You must be logged in to vote
6 replies
@tjheffner
Comment options

For an estimate, my current project has upwards of 80+ defined, call it 40 or so that need displayed. Various custom properties for colors, spacing vars, font families and sizes.

We run it once at build time for static builds, but continually during dev for safety/consistency, e.g. watch the specific variable file, run script on changes to ensure new variables are available in the .json that is read downstream.

I suppose I could manually parse them out in the future, or parse them after postcss has finished, but it was very nice to have this shortcut available & easily accessible through custom-properties.

@romainmenke
Comment options

romainmenke Mar 22, 2022
Maintainer Author

For an estimate, my current project has upwards of 80+ defined, call it 40 or so that need displayed.

That is a non-trivial amount :)

We run it once at build time for static builds, but continually during dev for safety/consistency, e.g. watch the specific variable file, run script on changes to ensure new variables are available in the .json that is read downstream.

Having to do this manually would be prone to errors and having a single source of truth is much better.

Would your workflow remain in tact if you create a single file in json format with all the properties and import/inject that in Storybook and CSS?

Or is there a specific reason for using CSS as the "data source" ?

@romainmenke
Comment options

romainmenke Mar 22, 2022
Maintainer Author

Also important to note that these changes are not rolling out soon.
Our main concern is that these features are embedded in a plugin that has a different purpose.

The plan is to split things up and create new plugins that are dedicated to the use cases people report in this discussion.

Knowing how it is being used will allow us to better define the replacement plugins.

@tjheffner
Comment options

Would your workflow remain in tact if you create a single file in json format with all the properties and import/inject that in Storybook and CSS?

Unfortunately no :(

Or is there a specific reason for using CSS as the "data source" ?

In my case, this is a rather large legacy project that is fed styles from multiple sources (drupal core, other teams, third party vendors, etc) with a variety of different custom properties defined throughout those sources. Our use case needs the ability to gather the upstream defined properties in addition to our set of properties on top for easier override management. So the exportTo shortcut was a life saver here!

If we were starting from scratch, I would absolutely define the properties in .js / json up-front and filter them out that way, but I'm about three years too late for that decision to be feasible for us at the current time.

@romainmenke
Comment options

romainmenke Mar 22, 2022
Maintainer Author

drupal core, other teams, third party vendors, etc

Good point.

In that case you actually need to move "data" from one thing you don't control to another you also don't really control to validate your own work. Extracting is the only option.

Comment options

Compiled with problems:

WARNING in ./src/App.css (./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js!./src/App.css)

Module Warning (from ./node_modules/postcss-loader/dist/cjs.js):
Warning

(1:1) postcss-custom-properties: "importFrom" and "exportTo" will be removed in a future version of postcss-custom-properties.
We are looking for insights and anecdotes on how these features are used so that we can design the best alternative.
Please let us know if our proposal will work for you.
Visit the discussion on github for more details. #192

You must be logged in to vote
5 replies
@romainmenke
Comment options

romainmenke Mar 22, 2022
Maintainer Author

@buxiangshede

This is not a compilation problem, just a deprecation warning.
Is this warning causing issues somehow?

@MaksymShutiak
Comment options

@romainmenke yes , this warning broke webpack cache when you build your app

@romainmenke
Comment options

romainmenke Apr 7, 2022
Maintainer Author

Can you share the tool/plugin/loader you are using to apply PostCSS with webpack?
Emitting a warning should not break webpack caching. So this seems like an issue on their end.
It would be good for us (csstools) to know more about this as from our perspective emitting a warning seems harmless.

@Alnipet
Comment options

image

I don't can start my Vue-CLI project

@romainmenke
Comment options

romainmenke Nov 26, 2022
Maintainer Author

@Alnipet This error is throw because you are using the importFrom feature in postcss-custom-properties. This feature has been removed in the most recent major version.

You have multiple options :

  • use postcss-design-tokens (best option)
  • do not upgrade postcss-custom-properties for now and revert to the previous version.
Comment options

Usage

I am using postcss-env-function to emulate env() until it "becomes available" in some standard way.

My usage of env() is for "static" custom properties, contrasting with var() (which I also use) for "dynamic" custom properties.

I have values that need not change at runtime. I thought env() was the way to do that. (var() values can change at runtime.)

Questions

  1. Is my usage misguided?
  2. Why are the solutions proposed thus far not using env() syntax?
  3. Is there a better way to do what I want, so that I don't bother this thread with my ignorance?
You must be logged in to vote
3 replies
@Antonio-Laguna
Comment options

@tacc-wbomar thanks for posting this :)

env function is available to insert the value of agent-defined environment variable into your CSS. This is the browser. If you check the CSSDB entry: https://cssdb.org/#environment-variables you'll see that it actually has great support. However both the plugin and the CSSDB outline a use-case that's not actually valid.

The variables available are:

  • safe-area-inset-bottom
  • safe-area-inset-left
  • safe-area-inset-right
  • safe-area-inset-top
  • titlebar-area-height
  • titlebar-area-width
  • titlebar-area-x
  • titlebar-area-y

The spec is still in flux (although it hasn't changed much) but it shouldn't be meant to be used like a values injector but rather as a way for the browser to pass information to the stylesheet. For example, the safe-are-inset-* is meant to be used as coordinates in which you can paint content without risking being cut by the shape of a non-rectangular display ( watches ) whereas is 0 on rectangular (normal) displays.

I think that once we advance with the design-tokens plugin, this is the exact use-case that the plugin is perfect for :)

To answer directly your questions one to one:

  1. Sadly yes. But this is caused by the plugin not being made to actually polyfill the spec but as a mutation of custom properties.
  2. Mostly because postcss-preset-env is meant to be a plugin-pack that polyfills, to the best of our ability, CSS specs. env plugin is not doing that which is why we want to deprecate it.
  3. The only way right now would be with something such as https://www.npmjs.com/package/postcss-replace but I think tokens will have more meaning.

All of this being said, this won't happen tomorrow or in a month. We're gathering use cases and sensing what would we need to cover and, in any case, you'd still be able to use the deprecated plugin manually when / if that happens.


Just realized this is rather long 🙃 . Please let me know if you got further questions and/or suggestions!

@wesleyboar
Comment options

All helpful information. Thank you. But now I learn my master plan for supporting custom branding relies on misused syntax.

Regarding alternatives, I imagine I could use postcss-replace or postcss-functions to perform what I currently do for env() but use any function-like syntax. If appropriate to do so here, please evaluate.

Alternative to env() for Use Case as Constants

Custom function to create "static/constant" custom properties.

Goal:

.foo {
  color: const(accent-color);
}

Solutions: ⚠️ untested

  • postcss-replace option pattern set to
    const\(\s?([^\s]+?)\s?\) /* escaped or not, docs are vague */
    ⚠️ quoted and escaped entities may not be handled correctly by regex (from reply)
  • postcss-function and a function like:
    function const( key ) {
      const value = constants[ key ];
      return value;
    }
    … it is okay to continue to use postcss-env-function (from reply)
@romainmenke
Comment options

romainmenke Mar 25, 2022
Maintainer Author

A regexp based find/replace might give issues. (are quoted or escaped entities handled correctly?)
But you can keep on using env() for now until we have a good replacement.

Most important to us at the moment is to get the replacement right :)
Only when we have something solid and tested by users will we start removing the deprecated features.

Comment options

May be a bit of an unconventional use but we are using the importFrom to be able to be able to theme our CSS output.
We have an initial set of variables that all the themes are initially based off and then have overrides for particular themes and concatenate the settings before passing them through to the processor.

We started using this feature with the package "postcss-css-variables" which allows you to pass in additional variables, but have had to move packages due to some output issues with that package.

const getProcessors = ( coreCssVars, customCssVars ) => {
	'use strict';

	const twentyTwentyOneCssVarsPath = '../../assets-raw/core/base/base/css/variables-colours-fonts-logos-2021.js';
	delete require.cache[ require.resolve( twentyTwentyOneCssVarsPath ) ]; // Delete cached variables file ready to reload

	const workingCssVarsPath = '../../assets-raw/core/base/base/css/variables-colours-fonts-logos.js';
	delete require.cache[ require.resolve( workingCssVarsPath ) ]; // Delete cached variables file ready to reload

	const vars = { preserve : false },
		workingCssVars = require( workingCssVarsPath ),
		twentyTwentyOneCssVars = require( twentyTwentyOneCssVarsPath );

	let baseVars;
	if ( coreCssVars === 'twentyTwentyOne' ) {
		baseVars = twentyTwentyOneCssVars.variables;
	} else if ( coreCssVars ) {
		baseVars = coreCssVars.variables;
	} else {
		baseVars = workingCssVars.variables;
	}

	if ( customCssVars ) {
		// Dots are spread operators; each step will take the previous one and overwrite but overwrite any overlaps
		vars.importFrom = [ { customProperties : { ...baseVars, ...themingConfig.css.overrides, ...customCssVars } } ];
	} else {
		vars.importFrom = [ { customProperties : { ...baseVars } } ];
	}

	return [
		cssImport(), // Reads @import rules
		postcssPresetEnv({ // Determines required css polyfills
			browsers : [ '> .2% and last 2 major versions', 'last 2 ChromeAndroid versions', 'Firefox ESR', 'ie 11', 'Edge >= 17' ],
			stage : 2,
		}),
		postcssCustomProperties( vars ), // Resolves variables to static values
	];
};
You must be logged in to vote
1 reply
@romainmenke
Comment options

romainmenke Mar 26, 2022
Maintainer Author

May be a bit of an unconventional use but we are using the importFrom to be able to be able to theme our CSS output.

This feature is not unconventional at all and this use case will be supported in the new plugin to import design tokens.

Generating multiple variants of stylesheets must be easy :)

Comment options

Update on design tokens :

see the wip plugin : https://github.com/csstools/postcss-plugins/tree/design-tokens--affectionate-sponge-94364bcfba/plugins/postcss-design-tokens#readme


On design tokens formats :

I have now tried multiple formats with most time spend on Style Dictionary and the Design Tokens specification.
Every format is highly problematic for stylesheet authors.

In general the implementations are flawed because they make the simple things a little bit easier but the hard things much harder. This is a common mistake for tools that integrate multiple existing systems.

To keep things simple, choices have been made that limit design tokens to a subset of features of all other systems involved. (With this I mean values and typings support)

Instead it should be embraced that styling is complicated and that CSS is a very advanced and feature rich language. Other contexts (iOS, android, ...) will have their own complexity that also can not be expressed by current design token formats.

Personally I think the current formats and proposals are a bad direction and will be harmful.
Any past learnings in file formats and how presentational values can be declared are ignored.

Design tokens as an idea are incredibly powerful and can speedup design and development when implemented correctly.

It should be magical because it just works.

How I think it should (or should not) work :

  1. Design tokens are a common vocabulary between designers and developers. This should be a core principle and lossy naming formats are in conflict with this principle.
  2. Design tokens should never place limitations on the final result a designer or developer can achieve.
  3. Design tokens should not require hacks in code or config.
  4. Design tokens should have rich IDE support (auto complete, details on hover, ...)
  5. Design tokens should separate design focussed features from development focussed features.

I do hope that all this evolves in a healthy way for everyone.


Current implementation

Exposing design tokens to CSS

File format

Because no single format has a good design I propose that we implement multiple.
This will place extra burden on us for maintaining this plugin, but I think it is the best direction.

Choosing a single format would be an endorsement of that format and they are all not good in their own way.

To support this a new at-rule is introduced :

@design-tokens url('./tokens-light.json') format('style-dictionary3');

This allows the plugin to pick a different token parser per imported file.
It also allows stylesheet authors to mix token sources generated by different versions of a single vendor. This is important given the high amount of issues in current formats. It gives us the ability to add new versions without making breaking changes all the time.

Include paths

Why

The plugin doesn't have any options to import design tokens.
This is all done through @design-tokens url(...).

This unlocks more features and convenience :

  • auto complete in editors to ease writing design-token('foo')
  • hover context on existing design-token('foo') values in CSS.
  • validation in editors.

When tokens are added to the plugin config, they are only exposed to the plugin itself and no other system or tool has access to your tokens.

Declaring everything in CSS will benefit developers most.

Resolving paths

@design-tokens url(...) doesn't support node module resolutions.
There is no good way to do this today that just works.

You will have to do things like this :

@design-tokens url('../../../node_modules/style-dictionary-design-tokens-example/style-dictionary.tokens.json') format('style-dictionary3');

Theming

To support theming or similar use cases, a when() function is available on @design-tokens.

With the existing plugins you would run the postcss-custom-properties multiple times, each time with different tokens in importFrom.

// define what the current build "is"
postcssDesignTokens({ is: ['dark'] })
@design-tokens url('./tokens-light.json') format('style-dictionary3');
@design-tokens url('./tokens-dark.json') when('dark') format('style-dictionary3');

.foo {
	color: design-token('color.background.primary');
}

Will use './tokens-dark.json'

This option is an array to allow more complex configurations.

postcssDesignTokens({ is: ['light', 'mobile', 'branded-blue'] })
postcssDesignTokens({ is: ['dark', 'mobile', 'branded-blue'] })
postcssDesignTokens({ is: ['light', 'tablet', 'branded-blue'] })
postcssDesignTokens({ is: ['dark', 'tablet', 'branded-blue'] })
postcssDesignTokens({ is: ['light', 'desktop', 'branded-blue'] })
postcssDesignTokens({ is: ['dark', 'desktop', 'branded-blue'] })

postcssDesignTokens({ is: ['light', 'mobile', 'branded-green'] })
postcssDesignTokens({ is: ['dark', 'mobile', 'branded-green'] })
postcssDesignTokens({ is: ['light', 'tablet', 'branded-green'] })
postcssDesignTokens({ is: ['dark', 'tablet', 'branded-green'] })
postcssDesignTokens({ is: ['light', 'desktop', 'branded-green'] })
postcssDesignTokens({ is: ['dark', 'desktop', 'branded-green'] })

The above example is likely complete overkill but it's designed for the most complex case I could think of. (please tell me if there is something this does not support)

@design-tokens url('./tokens/color_dark_branded-blue.tokens.json') when('dark', 'branded-blue') format('style-dictionary3');
@design-tokens url('./tokens/color_light_branded-blue.tokens.json') when('light', 'branded-blue') format('style-dictionary3');
@design-tokens url('./tokens/color_dark_branded-green.tokens.json') when('dark', 'branded-green') format('style-dictionary3');
@design-tokens url('./tokens/color_light_branded-green.tokens.json') when('light', 'branded-green') format('style-dictionary3');
@design-tokens url('./tokens/size_mobile.tokens.json') when('mobile') format('style-dictionary3');
@design-tokens url('./tokens/size_tablet.tokens.json') when('tablet') format('style-dictionary3');
@design-tokens url('./tokens/size_desktop.tokens.json') when('desktop') format('style-dictionary3');

This becomes a lot when you have a complex matrix, but you can create a single CSS file with all you @design-tokens rules once and import that one where needed.

Using design tokens in CSS

.foo {
	font-family: design-token('font.family.serif');
	font-size: design-token('size.font.small');
	color: design-token('color.font.base');
}

This uses a new function design-token(<string-token>).

Why a string

It has to be a string because al design token formats have ID's which are incompatible with CSS.
This also smoothes over any differences between token file formats.

You should not be typing these manually.
IDE autocompletion support is important to make this easy to use.

It also opens the door for namespaces.
Namespaces are currently not a feature anywhere but might become needed to resolve naming conflicts.

.foo {
	font-family: design-token('font.family.serif', 'design-team-a');
	font-size: design-token('size.font.small', 'design-team-b');
	color: design-token('color.font.base');
}

Why not env() or var()

env() does not currently have a spec for custom values.
Overloading env() will definitely cause conflicts in the future.

var() is for CSS custom properties. Design tokens aren't like these in any way.

Design tokens :

  • do not have inheritance/cascade behaviour
  • can't be overridden
  • aren't evaluated at runtime

All current tools that convert design tokens to CSS do use custom properties and this is a flawed approach.

Design tokens are only evaluated at compile time and are constants.
These are two concepts which have no equivalent in CSS.

Any CSS values with var() are also ignored in CSS downgrades/polyfills (postcss-preset-env). Not using them will make your projects more compatible with older browsers.

You must be logged in to vote
16 replies
@romainmenke
Comment options

romainmenke Mar 28, 2022
Maintainer Author

Is it appropriate for me to create a PR to your w.i.p. readme?

It is always appropriate to open a PR with anything ;)

@wesleyboar
Comment options

The when('default'), used explicitly, may be a confusing keyword. I imagine a developer could reasonably want to name their theme/condition 'default'. But I cannot think of perfect alternatives.

Details
  • when('default') — "My theme name or that plugin's keyword?" — newcomer
  • when(default) — A keyword for sure. But not a string… so invalid syntax in native CSS?
  • when('none') — "My theme name or that plugin's keyword?" — newcomer
@romainmenke
Comment options

romainmenke Mar 28, 2022
Maintainer Author

But I cannot think of perfect alternatives

It can also be a UUID.
My only reason for assigning it 'default' in code was to simplify all the logic.
No need to also check for empty string or false and assign a meaning to that.

The actual string value used has no meaning.

@romainmenke
Comment options

romainmenke Apr 3, 2022
Maintainer Author

@tacc-wbomar The readme has been updated :
https://github.com/csstools/postcss-plugins/blob/design-tokens--affectionate-sponge-94364bcfba/plugins/postcss-design-tokens/README.md

I did not add the full range of possibilities to prevent making the readme overly long and complicated. But I hope the current examples do add some clarity.

It can also be a UUID.
My only reason for assigning it 'default' in code was to simplify all the logic.
No need to also check for empty string or false and assign a meaning to that.

This is now something random to avoid conflicts when authors use when('default').

@wesleyboar
Comment options

Those are great changes @romainmenke.

I'm a little slow to open PRs, sorry. Also, I prefer your simplicity compared to what I had planned.

Comment options

We use exportTo in a custom Webpack loader which makes media queries like the following ones available in JS, e.g. for usage in media and sizes attributes of images.

queries.css

@custom-media --media-phablet (30em <= width < 48em);
@custom-media --media-tablet (48em <= width < 62.5em);

mediaQueryLoader.js

const postcss = require('postcss');
const postcssPresetEnv = require('postcss-preset-env');

module.exports = function customMediaLoader(content, map, meta) {

  const callback = this.async();

  postcss([postcssPresetEnv({
    stage: 1,
    preserve: true,
  })])
    .process(content, { from: this.resourcePath })
    .then((result) => {

      postcss([postcssPresetEnv({
        stage: 1,
        exportTo: (variables) => {
          if ('customMedia' in variables) {
            const moduleContent = `export default ${JSON.stringify(variables.customMedia)}`;
            callback(null, moduleContent, map, meta);
          }
        },
      })])
        .process(result, { from: this.resourcePath })
        .catch(callback);

    })
    .catch(callback);

};

Usage

import queries from '@/styles/queries.css';
const mediaTablet = queries['--media-tablet'] // '(min-width: 48em) and (max-width: 62.499em)'
You must be logged in to vote
2 replies
@romainmenke
Comment options

romainmenke Apr 4, 2022
Maintainer Author

Nice use case!
I hadn't considered using the CSS source code to generate image attributes.

Seems important for this that plugins (like media query ranges) are applied to these values before they are exported?

@jangarcia
Comment options

Yes, exactly!

Comment options

We currently use importFrom to include a global file of custom properties.

e.g.
theme.css

:root {
  /* Primary colors */
  --color-primary-1: #153247;
  --color-primary-2: #1d4f73;
  --color-primary-3: #57a9e3;
  --color-primary-4: #abdcff;

  /* etc. */
}
 importFrom: 'path/to/theme.css',
You must be logged in to vote
0 replies
Comment options

currently we have some separate css files with vars, import/export used to generate js\ts defs

You must be logged in to vote
0 replies
Comment options

We're using importFrom to feed values from JS into the CSS world, where the same file is used as a module in other parts of the JS world (Material UI theming), hence it is one source of truth for basic colors.

/* eslint-disable sort-keys */
// eslint-disable-next-line unicorn/prefer-module
module.exports = {
  customProperties: {
    '--black': '#000',
    '--dark-grey': '#323232',
    '--grey': '#666',
    '--light-grey': '#f0f0f0',
    '--white': '#fff',
    get '--primary'() {
      return this['--dark-navy-blue'];
    },
    get '--secondary'() {
      return this['--navy-blue'];
    },
    get '--tertiary'() {
      return this['--cyan'];
    },
    get '--success'() {
      return this['--grass'];
    },
    get '--warning'() {
      return this['--orange-red'];
    },
    /* Brand Colors */
    '--cyan': '#89D4D9',
    '--dark-navy-blue': '#112130',
    '--navy-blue': '#143B55',

    /* Supplementary Colors (mostly for chart curves) */
    '--aqua': '#39a9d3',
    '--earth': '#8b572a',
    '--grass': '#88ba4e',
    '--lavender': '#9177d5',
    '--orange-red': '#FF4500',
    '--rose': '#d44250',
    '--light-rose': '#FFB6C1',
    '--sun': '#eeb541',
    '--magenta': '#ca54ab',

    /* Shadow Colors */
    '--box-shadow-default': '0 2px 1px -1px rgba(0,0,0,0.2)',
    '--box-shadow-focus': '0 0 0 0.2rem rgba(0,123,255,.25)',

    '--border-radius-default': '2px',
  },
};
/* eslint-enable sort-keys */
You must be logged in to vote
0 replies
Comment options

How can I silence this warning BTW? It is all over the place during compilations

You must be logged in to vote
1 reply
@romainmenke
Comment options

romainmenke Apr 6, 2022
Maintainer Author

https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-properties#disabledeprecationnotice

{
  "disableDeprecationNotice": true
}

When using preset-env :

{
  "features": {
    "custom-properties": {
      "disableDeprecationNotice": true
    }
  }
}
Comment options

Hey @romainmenke we use the custom properties plugin to expose our css color variables to JS so that they can be used in the codebase for inline styles where inline styles are required for color or for libraries which requires colors to be passed through props as valid color value.

We don't want anyone to use the hardcoded colors in our codebase because they are hard to manage and any one can change hex easily and sometime can use colors which are outside of our design system

You must be logged in to vote
0 replies
Comment options

Hi there!

We have just released PostCSS Design Tokens

Our hope is that this will be a more reliable and controlled way to inject values into your CSS.
You can read more on this here : https://github.com/csstools/postcss-plugins/wiki/Why-we-think-PostCSS-Design-Tokens-is-needed

This is not a (drop-in) replacement for any of the plugins with importFrom or exportTo
Those will be created separately

You must be logged in to vote
0 replies
Comment options

My use of importFrom and exportTo is pretty quirky, so feel free to ignore this as a valid use case, but thought it might be interesting in terms of what it enabled for me.

I use hugo to build my static site and dev builds simply include all css and all js defined in the themes. In production it includes no CSS or JS and relies on a build process to add/inject the required assets.

The build process looks for files following a naming convention of <html element name, classname>[-inline|-sync|-async].css (and JS). Once the build process has the set of files, it parses each HTML file and adds style, link and link[preload] tags to include the files needed to render the page correctly.

With this set up I end up with lots of small CSS files tied to HTML elements and classnames. Using importFrom I can import variables from css files in a variables/ directory which allows postcss to add a fallback value to my css without needing to @import any files to define the css vars.

exportTo I only just discovered today. I'm move away from preserve: false so that I can css variables to define a light and dark theme for my site. I quickly realized I wanted to include all the CSS variables in my site; One option is to define a css file that imports all of the files under variables/ which I can inline in my pages. However, using exportTo means I don't need to extra file with manual curated @imports and instead can rely on postcss.

You must be logged in to vote
0 replies
Comment options

I'm using importFrom for variables.css, also mixins/ folder.

https://github.com/codetot-web/ct-bones/blob/production/webpack.config.js#L71

I also use exportTo for exporting variables.css to single file where I can include as inline style.

https://github.com/codetot-web/ct-bones/blob/production/codetot/assets.php#L136

You must be logged in to vote
0 replies
Comment options

Hi there 👋

Where Design Tokens are great for inserting values into CSS and form a natural successor for importFrom we have been looking for a while what to do with exportTo.

We ended up creating postcss-extract.

It allows you to extract bits of CSS and it is powered by queries written in CSS.

The feature set will grow over time and we think it is the better solution:

  • one plugin for all CSS data exports
  • no file system usage, so can be used in the browser, deno, ...
  • can grow independently from the plugins focussed on polyfilling native features.
You must be logged in to vote
3 replies
@wesleyboar
Comment options

That's creative.

  1. Why is it powered by queries written in CSS?1
  2. What is i in rule[selector*=":root" i] > decl[variable]?

Footnotes

  1. If the query is entered as JavaScript (queries: { ... }), then I think a developer may have an easier time writing JavaScript object(s) to declare the, than they would learning a unique interpretation of CSS syntax. (I admit the CSS-inspired string value could be faster, once learned.) If the query is meant to be written in a CSS file (perhaps to serve as a CSS-authored config), then... interesting... the query looks like only a selector though, so what is the rest of the ruleset?

@romainmenke
Comment options

romainmenke Nov 8, 2022
Maintainer Author

Why is it powered by queries written in CSS

Two reasons :

  • the intended users are CSS authors.
  • CSS selector syntax is a precise and terse way to write matching rules without limiting the complexity of these rules.

CSS is the best tool for the job here in our opinion.
(also see : https://docs.npmjs.com/cli/v8/using-npm/dependency-selectors)

The equivalent in JavaScript would require a very complex and deeply nested config, filled with obscure flags and regexp.

If you do however need a complex matcher and are much more comfortable with JavaScript it might be easier to just write a custom PostCSS plugin specifically for your use case. A plugin that does one thing for one person can be much simpler than anything we create.

What is i in rule[selector*=":root" i] > decl[variable]?

The i makes the matching with :root case insensitive.

/* also matches */
:ROOT {}
@wesleyboar
Comment options

Clear. Sensible. Cool.

And wow. NPM dependency-selectors blew my mind. I did not expect CSS syntax to be so influential.

Comment options

Hi,

Thanks for doing what you're doing.

We're using postcss-custom-media plugin and our setup is:

// file: postcss.config.js
const postcssCustomMedia = require("postcss-custom-media")

module.exports = {
  plugins: [
    ...
    postcssCustomMedia({
      importFrom: ["./path/to/custom-media.css"],
    }),
    ...
}
/* file: ./path/to/custom-media.css */
@custom-media --extra-small-and-above (min-width: 376px);

We're getting, when upgrading postcss-custom-media from 8.0.2 to 9.0.1,

Module build failed (from ../node_modules/postcss-loader/dist/cjs.js):
  Error: [postcss-custom-media] "importFrom" is no longer supported

Please advice. Thanks.

You must be logged in to vote
13 replies
@mihkeleidast
Comment options

@romainmenke since this is not something that is implemented in any browsers, can you help me understand what you'd expect to happen (per the feature spec) when:

  1. I have a media.css file that defines the custom media queries:
// media.css
@custom-media --small-viewport (max-width: 30em);
  1. And then another CSS file that uses it:
// main.css
@media (--small-viewport) {
	/* styles for small viewport */
}
  1. And both of these would be just linked up in HTML:
<link rel="stylesheet=" href="./media.css" />
<link rel="stylesheet=" href="./main.css" />

Per the custom media queries spec, would this work or not, i.e can the custom media be defined in a separate file from where it is used in?

  • If it would not work, then I agree that from the perspective of the plugin importFrom is an additional feature that does not polyfill what the browser would do.
  • If it would work, then cross-file usage is a spec feature that should be polyfilled by some plugin.

If I read the spec, then I don't see any limitations there that would say that the custom media definitions need to be in the same file, i.e. they should work cross-file, and as such, IMO this is a feature that should be polyfilled. So I'd leave out JSON definitions, but an import from CSS file should still work. Any thoughts on this?


As for #734, I saw that now (when I ran into issues after dependabot opened an update PR), not before it was closed. I'd assume a lot of users are going to run into these issues in the near future, so perhaps someone (or even me) will pick up that solution. Hard to keep up do date with what is going on in all the dependency repos before running into actual issues :)

@romainmenke
Comment options

romainmenke Feb 1, 2023
Maintainer Author

that should be polyfilled by some plugin.
So I'd leave out JSON definitions, but an import from CSS file should still work

This feature and the plugin for it exist.
It is @import and https://www.npmjs.com/package/postcss-import

Since there is a native solution we don't think there should be hacks in individual polyfills for unrelated features. importFrom and exportTo should never have been added to any of these plugins.

Hard to keep up do date with what is going on in all the dependency repos before running into actual issues

Absolutely!
We also don't have a good channel that we know will reach all affected users before it will cause issues :)


That said, we think there are a few issues that are created by how other tools/frameworks work.
By processing CSS completely separately it becomes impossible to have shared global CSS/state.

We are now considering fixing this issue specifically with a new plugin :

        'postcss-global-data': {
            files: [
                './src/assets/styles/media-queries.css',
            ]
        },

This would inject and expose CSS and remove it again at the end.
It would allow other features to hook into and depend on the shared CSS without bloating all files.

It allows you to configure your toolchain for what you know will come together on a single page in a browser.

This is not a polyfill and would never be part of postcss-preset-env.
It would be a dev tool to work around how certain frameworks process CSS.

Thoughts?

@mihkeleidast
Comment options

This feature and the plugin for it exist.
It is @import and https://www.npmjs.com/package/postcss-import

Can't agree here, imports in individual CSS files are quite different from just different globally linked up assets in HTML. Using this would require the source of those individual files to be changed, if I understand correctly. I would have to add the imports and keep them basically forever, since I don't know if all of my library's users browsers support the feature or not. Not sure if I even can add the imports, since I use vanilla-extract.

I would expect I can author my styles in a future-compliant way (i.e. if target browsers do start supporting custom media, I don't need to edit/remove anything as everything is already up to standard). This is what my example above with two separate CSS files is. If my browserslist at some point supports custom-media (I know this is a hypothetical for now), PostCSS does not need to replace anything my source files, then the media.css linked up globally would provide the custom media definitions for all other CSS files used in the document.

I can understand some need for a separation of concerns, but I don't really get why such cross-file usage is not a "spec feature" by your standards that should be polyfilled.

postcss-global-data seems interesting, essentially it would allow me to tell PostCSS what other files I intend to link up globally, right? I think this would work around the issue with custom media definitions in a separate file indeed, so would be most welcome.

@romainmenke
Comment options

romainmenke Feb 7, 2023
Maintainer Author

@mihkeleidast We have released the plugin : https://www.npmjs.com/package/@csstools/postcss-global-data

Can you check this out and let us know if everything works as expected?

@mihkeleidast
Comment options

Yup, this is working great now! Thanks for the quick turnaround on this one!

Comment options

Hi everyone 👋

We have written the code for the drop-in replacements but we decided against publishing and maintaining these ourselves.

You can read more about it here : #734

TL;DR; if anyone is willing to take over maintenance of the functionality behind importFrom/exportTo please let us (and each other) know.

The source code is available and anyone is free to take this and publish their own plugins based on that.


To summarize :

exportTo

For exportTo we advice everyone to switch over to @csstools/postcss-extract. We think it is a more powerful tool and it isn't limited to a small set of features, anything can be exported.

If you do depend on exportTo and migrating is absolutely impossible you should consider taking the source code from #734 and publish/maintain your own version.

importFrom

Usage for importFrom seems to range between these things :

  • theming
  • design tokens
  • workaround for issues with Webpack / PostCSS Loader

For theming and design tokens we advice everyone to switch over to @csstools/postcss-design-tokens.

This tool is build specifically for theming and design tokens and unlocks better features, like auto complete in editors for injected tokens.

For issues with bundlers (e.g. Webpack / PostCSS Loader) we suggest to use postcss-import. It handles @import in CSS in a way that closely resembles how @import works in Browsers and applies all plugins to all CSS correctly.

If you do depend on importFrom and migrating is absolutely impossible you should consider taking the source code from #734 and publish/maintain your own version.


We now consider this issue resolved.

We are aware that no breaking changes would be better for everyone using importFrom/exportTo but this change will allow us to keep postcss-preset-env healthy for years to come.

You must be logged in to vote
1 reply
@yashsway
Comment options

Well written guide. Thank you so much for your work!

Answer selected by romainmenke
Comment options

https://github.com/GoogleChromeLabs/postcss-jit-props This plugin is also a good replacement for importFrom with custom properties.

You must be logged in to vote
0 replies
Comment options

https://github.com/allmyfutures/postcss-custom-media-generator This plugin seems to be a good replacement for importFrom with custom media.

You must be logged in to vote
0 replies
Comment options

https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-global-data#readme

This plugin is a good replacement for importFrom in specific frameworks/stacks.

You must be logged in to vote
0 replies
Comment options

Is it possible to export css variables to an object now?

You must be logged in to vote
1 reply
@romainmenke
Comment options

romainmenke Feb 9, 2026
Maintainer Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Morty Proxy This is a proxified and sanitized view of the page, visit original site.