{"id":15015785,"url":"https://github.com/evoactivity/ember-modern-css","last_synced_at":"2026-03-27T02:30:15.926Z","repository":{"id":45430770,"uuid":"513625962","full_name":"evoactivity/ember-modern-css","owner":"evoactivity","description":"Forget everything you know about CSS + Ember. If you use any of the following ember-postcss, ember-css-modules, ember-component-css then this guide is for you.","archived":false,"fork":false,"pushed_at":"2023-07-14T23:40:04.000Z","size":974,"stargazers_count":32,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-31T11:41:18.607Z","etag":null,"topics":["css","ember"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/evoactivity.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-13T18:13:43.000Z","updated_at":"2024-06-26T18:45:41.000Z","dependencies_parsed_at":"2024-09-20T12:02:06.891Z","dependency_job_id":"8e776f01-2309-4857-b616-35fc4d791852","html_url":"https://github.com/evoactivity/ember-modern-css","commit_stats":{"total_commits":12,"total_committers":4,"mean_commits":3.0,"dds":0.25,"last_synced_commit":"eea9f8e9c20699e7adfe894c0b94eb739973cafd"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evoactivity%2Fember-modern-css","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evoactivity%2Fember-modern-css/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evoactivity%2Fember-modern-css/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evoactivity%2Fember-modern-css/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/evoactivity","download_url":"https://codeload.github.com/evoactivity/ember-modern-css/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223510342,"owners_count":17157306,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","ember"],"created_at":"2024-09-24T19:47:55.486Z","updated_at":"2026-03-27T02:30:10.892Z","avatar_url":"https://github.com/evoactivity.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ember + Modern CSS\n\nForget everything you know about CSS + Ember. If you use any of the following `ember-cli-postcss`, `ember-css-modules`, `ember-component-css` then this guide is for you.\n\nTogether we are going to take an embroider enabled Ember app and configure a webpack pipeline for our application's CSS using PostCSS and tailwind.\n\nThis guide will not go over enabling embroider in your application, I will be using a fresh ember app with full embroider compatibility (route splitting included).\n\nAn example app is available at https://github.com/evoactivity/ember-modern-css\n\n## Goal\n\nAn ember app using tailwind with livereload and full tailwind JIT compatibility. This will be achieved with webpack's `postcss-loader` addon, opening up the rest of the postcss ecosystem.\n\n## Benefits\n\nUsing webpack to bundle our CSS opens up our Ember app to the rest of the CSS ecosystem. We no longer need to reach for the addons mentioned above or write our own addons for dealing with the ember-cli CSS pipeline. We just ignore the legacy CSS pipeline and by doing so we get some easy wins.\n\n1. Route split, lazy loading CSS\n2. CSS Modules out of the box\n3. If a webpack addon exists you can use it (more on this in future articles)\n\n## Dependencies\n\nWe are aiming to use tailwind and PostCSS so we must install them both along with some other dependencies.\n\n```bash\nnpm add postcss postcss-loader tailwindcss autoprefixer cssnano --save-dev\n```\n\nCreate our config files in the project root\n\n```js\n// ./postcss.config.js\n\nconst env = process.env.EMBER_ENV || 'development';\n\nconst plugins = [\n  require('tailwindcss/nesting'),\n  require('tailwindcss')({ config: './tailwind.config.js' }),\n  require('autoprefixer'),\n];\n\nif (env === 'production') {\n  plugins.push(\n    require('cssnano')({\n      preset: 'default',\n    })\n  );\n}\n\nmodule.exports = {\n  plugins,\n};\n```\n\n```js\n// ./tailwind.config.js\n\nconst path = require('node:path');\nconst defaultTheme = require('tailwindcss/defaultTheme');\nconst appEntry = path.join(__dirname, 'app');\nconst relevantFilesGlob = '**/*.{html,js,ts,hbs,gjs,gts}';\n\nmodule.exports = {\n  content: [path.join(appEntry, relevantFilesGlob)],\n  theme: {\n    extend: {\n      fontFamily: {\n        sans: ['Inter var', ...defaultTheme.fontFamily.sans],\n      },\n    },\n  },\n  plugins: [],\n};\n```\n\nWe need to tell eslint these are node files, open `.eslintrc.js`\n\n```js\n// .eslintrc.js\n  // ...\n  overrides: [\n  // node files\n  {\n    files: [\n      './.eslintrc.js',\n      './.prettierrc.js',\n      './.template-lintrc.js',\n      './ember-cli-build.js',\n      './testem.js',\n      './blueprints/*/index.js',\n      './config/**/*.js',\n      './lib/*/index.js',\n      './server/**/*.js',\n      // add new config files\n      './postcss.config.js',\n      './tailwind.config.js',\n    ],\n  // ...\n```\n\n## Webpack Configuration\n\nAssuming you already have embroider installed and fully compatible we need to update the webpack config. Open `ember-cli-build.js`\n\nNear the bottom you should have your embroider config\n\n```js\nreturn require('@embroider/compat').compatBuild(app, Webpack, {\n  staticAddonTestSupportTrees: true,\n  staticAddonTrees: true,\n  staticHelpers: true,\n  staticModifiers: true,\n  staticComponents: true,\n  splitAtRoutes: ['route1', 'route2'],\n  packagerOptions: {\n    webpackConfig: {},\n  },\n  extraPublicTrees: [],\n});\n```\n\nLet's add our new config, I have added comments to each line\n\n```js\nfunction isProduction() {\n  return EmberApp.env() === 'production';\n}\n\nreturn require('@embroider/compat').compatBuild(app, Webpack, {\n  staticAddonTestSupportTrees: true,\n  staticAddonTrees: true,\n  staticHelpers: true,\n  staticModifiers: true,\n  staticComponents: true,\n  splitAtRoutes: ['route1', 'route2'],\n  packagerOptions: {\n    // publicAssetURL is used similarly to Ember CLI's asset fingerprint prepend option.\n    publicAssetURL: '/',\n    // Embroider lets us send our own options to the style-loader\n    cssLoaderOptions: {\n      // don't create source maps in production\n      sourceMap: isProduction() === false,\n      // enable CSS modules\n      modules: {\n        // global mode, can be either global or local\n        // we set to global mode to avoid hashing tailwind classes\n        mode: 'global',\n        // class naming template\n        localIdentName: isProduction()\n          ? '[sha512:hash:base64:5]'\n          : '[path][name]__[local]',\n      },\n    },\n    webpackConfig: {\n      module: {\n        rules: [\n          {\n            // When webpack sees an import for a CSS file\n            test: /(node_modules\\/\\.embroider\\/rewritten-app\\/)(.*\\.css)$/i,\n            use: [\n              {\n                // use the PostCSS loader addon\n                loader: 'postcss-loader',\n                options: {\n                  sourceMap: isProduction() === false,\n                  postcssOptions: {\n                    config: './postcss.config.js',\n                  },\n                },\n              },\n            ],\n          },\n          {\n            test: /(node_modules\\/\\.embroider\\/rewritten-app\\/)(.*\\.(jpg|jpeg|png|woff|woff2|eot|ttf|svg))$/,\n            use: [\n              {\n                // include a image/font standard loader for certain\n                // Tailwind/CSS features.\n                loader: 'url-loader',\n                options: {\n                  limit: 8192,\n                },\n              },\n            ],\n          },\n        ],\n      },\n    },\n  },\n  extraPublicTrees: [],\n});\n```\n\n## Let's talk directory structure\n\n### Legacy `./styles` and `./styles/app.css`\n\n`ember-cli` expects `styles/app.css` to exist, but we do not want any css inside of this file. Just an empty app.css is required.\n\nWe cannot delete this directory or file and we cannot import from this directory so for all intents and purposes this directory does not exist.\n\nHopefully we can delete it one day.\n\n### New `./assets` directory\n\nSince we have limitations on our `./styles` directory this will be where our entrypoint CSS is going to be created.\n\n```bash\nmkdir -p app/assets \u0026\u0026 touch app/assets/styles.css\n```\n\nLet's add our tailwind styles\n\n```css\n/* ./app/assets/styles.css */\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n```\n\n## Importing CSS\n\nWe now need to import our CSS into our application. We are going to import our CSS into our `./app.js`. The reason for this is to ensure load order when we build for production. Webpack injects `\u003clink\u003e` tags in the order they are imported and we want our entrypoint to always be first.\n\n```js\nimport Application from '@ember/application';\nimport Resolver from 'ember-resolver';\nimport loadInitializers from 'ember-load-initializers';\nimport config from 'ember-modern-css/config/environment';\n// import our applications style entrypoint\nimport './assets/styles.css';\n\nexport default class App extends Application {\n  modulePrefix = config.modulePrefix;\n  podModulePrefix = config.podModulePrefix;\n  Resolver = Resolver;\n}\n\nloadInitializers(App, config.modulePrefix);\n```\n\nAt this point your should have tailwind installed and your CSS will rebuild on changes to your templates and js files.\n\n## CSS Modules\n\nNow that we can import our CSS files we can make use CSS modules with our components.\n\nSay we have\n\n```\n./app/components/my-widget/\n  - index.js\n  - index.hbs\n```\n\nWe can co-locate our styles along side our components.\n\n```\n./app/components/my-widget/\n  - index.js\n  - index.hbs\n  - styles.css\n```\n\n```css\n/* We mark the classes we want hashed with :local() */\n:local(.root) {\n  background: red;\n}\n\n:local(.anotherHashedClass) {\n  background: green;\n}\n\n.normalClass {\n  background: blue;\n}\n```\n\nOpen your component's index.js\n\n```js\nimport Component from '@glimmer/component';\n// import our styles, this time named\nimport styles from './styles.css';\n\nexport default class MyWidget extends Component {\n  // we need our styles to be accessible from our template\n  // so add a property in your class.\n  styles = styles;\n}\n```\n\nWhen built our `.root` class will become something like `.h4e` so we need to lookup the correct class string. `styles` is created as a property of the component, giving us access to `{{this.styles.myClassName}}` in our template.\n\n```hbs\n\u003ch1 class={{this.styles.root}}\u003eWidget\u003c/h1\u003e\n```\n\nThis can become tedious when using longer or multiple classes, or mixing hashed classes with non-hashed classes\n\nWouldn't it be nice if instead of this\n\n```hbs\n\u003ch1\n  class={{concat\n    this.styles.root\n    ' '\n    this.styles.anotherHashedClass\n    ' bg-red-100 mb-10'\n  }}\n\u003eWidget\u003c/h1\u003e\n```\n\nWe could write\n\n```hbs\n\u003ch1\n  class='{{styles this \"root anotherHashedClass\"}} bg-red-100 mb-10'\n\u003eWidget\u003c/h1\u003e\n```\n\nSo let's create a helper\n\n```bash\nember g helper styles\n```\n\n```js\nimport Helper from '@ember/component/helper';\n\nexport default class Styles extends Helper {\n  compute(params) {\n    const [context, classNames] = params;\n\n    if (typeof context?.styles === 'undefined') {\n      console.error(\n        'Import and assign your styles to your component class or controller class'\n      );\n      return '';\n    }\n\n    const classString = classNames\n      .split(' ')\n      .map((name) =\u003e {\n        if (context?.styles \u0026\u0026 context?.styles[name]) {\n          return context.styles[name];\n        }\n\n        console.error(`The class or id named '${name}' does not exist`);\n\n        return '';\n      })\n      .join(' ');\n\n    return classString;\n  }\n}\n```\n\nWe can now update our template\n\n```hbs\n\u003ch1 class={{styles this 'root'}}\u003eWidget\u003c/h1\u003e\n```\n\n## Typescript\n\nIf you are using typescript in your project it will complain about not being able to import .css files. To remedy this we need to tell typescript what to expect when importing a css file.\n\nOpen your `global.d.ts` and add\n\n```typescript\ndeclare module '*.css' {\n  const styles: { [className: string]: string };\n  export default styles;\n}\n```\n\n## Next Steps\n\nYou can now customize your PostCSS and Tailwind configs, add your own PostCSS addons and make it work for you and your team.\n\nOnce you have your CSS modernized I would suggest managing your images and fonts in a similar way. That will be my next article, using webpack's `asset/loader` for fonts and `responsive-loader` to generate responsive images from a single image import.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevoactivity%2Fember-modern-css","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fevoactivity%2Fember-modern-css","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevoactivity%2Fember-modern-css/lists"}