--- title: Adding CSS and JS description: Your components might need custom styles or custom JavaScript for many reasons. ---
Your components might need custom styles or custom JavaScript for many reasons.
Instead of using global stylesheet or script files, writing assets for each individual component has several advantages: - **Portability**: You can copy a component from one project to another, knowing it will keep working as expected. - **Performance**: Only load the CSS and JS that you need on each page. Additionally, the browser will have already cached the assets of the components for other pages that use them. - **Simple testing**: You can test the JS of a component independently from others. ## Auto-loading assets JinjaX searches for `.css` and `.js` files with the same name as your component in the same folder and automatically adds them to the list of assets included on the page. For example, if your component is `components/common/Form.jinja`, both `components/common/Form.css` and `components/common/Form.js` will be added to the list, but only if those files exist. ## Manually declaring assets In addition to auto-loading assets, the CSS and/or JS of a component can be declared in the metadata header with `{#css ... #}` and `{#js ... #}`. ```html {#css lorem.css, ipsum.css #} {#js foo.js, bar.js #} ``` - The file paths must be relative to the root of your components catalog (e.g., `components/form.js`) or absolute (e.g., `http://example.com/styles.css`). - Multiple assets must be separated by commas. - Only **one** `{#css ... #}` and **one** `{#js ... #}` tag is allowed per component at most, but both are optional. ### Global assets The best practice is to store both CSS and JS files of the component within the same folder. Doing this has several advantages, including easier component reuse in other projects, improved code readability, and simplified debugging. However, there are instances when you may need to rely on global CSS or JS files, such as third-party libraries. In such cases, you can specify these dependencies in the component's metadata using URLs that start with either "/", "http://," or "https://." When you do this, JinjaX will render them as is, instead of prepending them with the component's prefix like it normally does. For example, this code: ```html+jinja {#css foo.css, bar.css, /static/bootstrap.min.css #} {#js http://example.com/cdn/moment.js, bar.js #} {{ catalog.render_assets() }} ``` will be rendered as this HTML output: ```html ``` ## Including assets in your pages The catalog will collect all CSS and JS file paths from the components used on a "page" render on the `catalog.collected_css` and `catalog.collected_js` lists. For example, after rendering this component: ```html+jinja title="components/MyPage.jinja" {#css mypage.css #} {#js mypage.js #}

Lizard

The Iguana is a type of lizard

``` Assuming the `Card` and `Button` components declare CSS assets, this will be the state of the `collected_css` list: ```py catalog.collected_css ['mypage.css', 'card.css', 'button.css'] ``` You can add the `` and ` ``` ## Middleware The tags above will not work if your application can't return the content of those files. Currently, it can't. For that reason, JinjaX includes WSGI middleware that will process those URLs if you add it to your application. ```py from flask import Flask from jinjax import Catalog app = Flask(__name__) # Here we add the Flask Jinja globals, filters, etc., like `url_for()` catalog = jinjax.Catalog(jinja_env=app.jinja_env) catalog.add_folder("myapp/components") app.wsgi_app = catalog.get_middleware( app.wsgi_app, autorefresh=app.debug, ) ``` The middleware uses the battle-tested [Whitenoise library](http://whitenoise.evans.io/) and will only respond to the *.css* and *.js* files inside the component(s) folder(s). You can configure it to also return files with other extensions. For example: ```python catalog.get_middleware(app, allowed_ext=[".css", .js", .svg", ".png"]) ``` Be aware that if you use this option, `get_middleware()` must be called **after** all folders are added. ## Good practices ### CSS Scoping The styles of your components will not be auto-scoped. This means the styles of a component can affect other components and likewise, it will be affected by global styles or the styles of other components. To protect yourself against that, *always* add a custom class to the root element(s) of your component and use it to scope the rest of the component styles. You can even use this syntax now supported by [all modern web browsers](https://caniuse.com/css-nesting): ```sass .Parent { .foo { ... } .bar { ... } } ``` The code above will be interpreted as ```css .Parent .foo { ... } .Parent .bar { ... } ``` Example: ```html+jinja title="components/Card.jinja" {#css card.css #}

My Card

...
``` ```sass title="components/card.css" /* 🚫 DO NOT do this */ h1 { font-size: 2em; } h2 { font-size: 1.5em; } a { color: blue; } /* 👍 DO THIS instead */ .Card { & h1 { font-size: 2em; } & h2 { font-size: 1.5em; } & a { color: blue; } } /* 👍 Or this */ .Card h1 { font-size: 2em; } .Card h2 { font-size: 1.5em; } .Card a { color: blue; } ``` Always use a class **instead of** an `id`, or the component will not be usable more than once per page. ### JS events Your components might be inserted in the page on-the-fly, after the JavaScript files have been loaded and executed. So, attaching events to the elements on the page on load will not be enough: ```js title="components/card.js" // This will fail for any Card component inserted after page load document.querySelectorAll('.Card button.share') .forEach((node) => { node.addEventListener("click", handleClick) }) /* ... etc ... */ ``` A solution can be using event delegation: ```js title="components/card.js" // This will work for any Card component inserted after page load document.addEventListener("click", (event) => { if (event.target.matches(".Card button.share")) { handleClick(event) } }) ```