Generate declaration files for CSS modules
The package provides accurate types for CSS module files. It's designed with Ember projects in mind, but can be used with any JavaScript framework and build tool.
pnpm add -D type-css-modulesEnsure that CSS declaration files exist before types are checked. For example, you can write a pre-script.
/* package.json */
{
"scripts": {
"prelint:types": "type-css-modules <arguments>",
"lint:types": "ember-tsc --noEmit" // or "glint"
},
"devDependencies": {
"type-css-modules": "...",
"typescript": "..."
}
}You must pass --src to indicate the location of your CSS module files.
type-css-modules --src appYou can pass multiple values or use glob patterns to specify multiple locations.
type-css-modules --src app/components app/templates
type-css-modules --src "app/{components,controllers,templates}"Optional: Specify the project root
Pass --root to run the codemod somewhere else (i.e. not in the current directory).
type-css-modules --root <path/to/your/project>You can run into a couple of issues when prettier and type-css-modules run side-by-side.
type-css-modules uses quotation marks in declaration files so that we can always use class selector names as object keys. On the other hand, prettier removes quotation marks when it deems unnecessary. To separate formatting concerns, set quoteProps: 'preserve' for *.css.d.ts files:
/* prettier.config.mjs */
export default {
overrides: [
{
files: '*.css.d.ts',
options: {
quoteProps: 'preserve',
},
},
],
};Furthermore, if you run prettier as a standalone tool (i.e. not as a linter plugin), you can run into a concurrency issue because type-css-modules deletes declaration files before creating new ones. Make the two independent by adding *.css.d.ts to .prettierignore.
Yes! You may use *.module.css to indicate the stylesheets that are for CSS modules. type-css-modules will create declaration files with the extension *.module.css.d.ts.
The Prettier configuration (shown above) can remain as is.
To reduce complexity, type-css-modules expects you to follow the conventions of embroider-css-modules:
- Give the local scope to the styles that you own1
- Use the default import to import styles
Here are some examples that meet the syntax requirements.
Ember: Glimmer component
/* app/components/ui/page.css */
.container {
display: grid;
grid-template-areas:
"header"
"body";
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
height: calc(100% - 3em);
overflow-y: auto;
padding: 1.5rem 1rem;
scrollbar-gutter: stable;
}
.header {
grid-area: header;
}
.body {
grid-area: body;
}/* app/components/ui/page.ts */
import Component from '@glimmer/component';
import styles from './page.css';
export default class UiPageComponent extends Component {
styles = styles;
}Ember: <template> tag
/* app/components/ui/page.gts */
import { local } from 'embroider-css-modules';
import styles from './page.css';
<template>
<div class={{local styles "container"}}>
<h1 class={{styles.header}}>
{{@title}}
</h1>
<div class="{{styles.body}}">
{{yield}}
</div>
</div>
</template>And some counterexamples (what not to do):
Don't use the :local() pseudo-class selector
/* app/components/ui/page.css */
:local(.container) {
display: grid;
grid-template-areas:
"header"
"body";
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
height: calc(100% - 3em);
overflow-y: auto;
padding: 1.5rem 1rem;
scrollbar-gutter: stable;
}
:local(.header) {
grid-area: header;
}
:local(.body) {
grid-area: body;
}Don't use named imports to import styles
/* app/components/ui/page.gts */
import { container, header, body } from './page.css';
<template>
<div class={{container}}>
<h1 class={{header}}>
{{@title}}
</h1>
<div class="{{body}}">
{{yield}}
</div>
</div>
</template>1. With webpack, for example, you can configure mode to be a function that returns 'local' or 'global'. In stylesheets, you can use the :global() pseudo-class selector to refer to "things from outside."
- Node.js v22 or above
See the Contributing guide for details.
This project is licensed under the MIT License.