ESM Styles
Write CSS as JavaScript objects. The compiler converts JS → CSS.
Critical Rules
NO Ampersand (&) Syntax
// ❌ WRONG - will NOT work
button: {
'&:hover': { opacity: 0.9 },
'&.active': { color: 'red' }
}
// ✅ CORRECT
button: {
':hover': { opacity: 0.9 },
active: { color: 'red' }
}
HTML Tags vs Classes
Tags are recognized automatically. Non-tags become classes:
{
div: { margin: 0 }, // → div { margin: 0; }
p: { fontSize: '16px' }, // → p { font-size: 16px; }
card: { padding: '20px' } // → .card { padding: 20px; }
}
Underscore Conventions
{
modal: {
_button: { ... }, // → .modal.button (single _ = class with tag name)
__close: { ... } // → .modal .close (double __ = descendant class)
}
}
Pseudo-classes and Selectors
{
button: {
':hover': { opacity: 0.9 },
'::before': { content: '"→"' },
'> span': { marginLeft: '5px' },
'[disabled]': { cursor: 'not-allowed' }
}
}
Media Queries
{
container: {
maxWidth: '1200px',
'@mobile': { maxWidth: '100%' }, // named query from config
'@dark': { backgroundColor: '#222' } // theme selector
}
}
CSS Variables
For each key in media config, a helper module $<key>.mjs is generated:
// media: { theme: [...], device: [...] } → $theme.mjs, $device.mjs
import $theme from './$theme.mjs'
import $device from './$device.mjs'
export default {
button: {
backgroundColor: $theme.paper.bright,
padding: $device.spacing.md,
// For concatenation, use .var:
border: `1px solid ${$theme.paper.tinted.var}`
}
}
These modules give IDE autocomplete and show values. Without them → manual var(--name).
Limitations
No @keyframes support
Create a separate animations.css file, import globally, use animation names in styles:
/* animations.css */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
// component.styles.mjs
export const Modal = {
animation: 'fadeIn 0.3s ease-out'
}
Import Aliases
Path aliases can be configured in esm-styles.config.js (see sample.config.js in repo root):
aliases: {
'@': '.',
'@components': './components',
}
For IDE support (autocomplete, Cmd+click), add jsconfig.json to sourcePath folder (see sample-styles/source/jsconfig.json):
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["./*"] }
}
}
Project Conventions
Component Styles (PascalCase)
// styles/components/message.styles.mjs
export const Message = {
width: '90vw',
urgent: { backgroundColor: 'red' },
header: {
fontSize: '1.2rem'
}
}
// In React component
<article className="Message urgent">...</article>
Layout Styles
// styles/layout.styles.mjs
export const layout = {
home: { display: 'grid' },
inner: { maxWidth: '1200px' }
}
<div className="layout home">...</div>
Build
Before running build commands, ask the user:
- "Is
npx watchalready running?"
If yes → styles compile automatically on save, no action needed.
If no → run npx build from the esm-styles package folder.
Note: Commands run from the folder where esm-styles is installed (e.g. packages/styles).
References
- Compiler rules: See references/compiler.md for complete JS→CSS translation
- Configuration: See references/config.md for esm-styles.config.js
- Project structure: See references/conventions.md for file organization
