Separation of Concerns (SoC) for UI Styling and Scripts
When to Use
Use this skill whenever you write or restructure custom CSS or frontend JavaScript in a project that spans multiple pages, regardless of framework. It applies when adding a new page, adding a reusable component (modal, card, quote block), or cleaning up a stylesheet or script that has grown too large.
Before You Start
- Inspect the existing project structure. If the project already has a clear convention for stylesheets or scripts (e.g. CSS Modules, a
components/ folder with co-located files, a hooks/ or lib/ folder), follow it instead of introducing a new one.
- If the project uses a utility framework such as Tailwind, prefer utility classes and only write custom CSS for what utilities cannot express cleanly. The CSS rules below then apply to that custom CSS.
The Three Tiers
Both CSS (S1) and JavaScript (S2) are sorted into the same three tiers. Never put everything into a single file by default, and never duplicate the same code across several pages.
| Tier |
Scope |
Where it lives |
| 1 |
Global |
Used on all pages |
| 2 |
Shared component |
Used on several, but not all, pages |
| 3 |
Page-specific |
Used on exactly one page |
As a guideline, "short" means roughly under 30 lines. Readability of the page file matters more than an exact count. When in doubt about Tier 2, prefer a separate component file: it keeps the global file small and makes the component easy to find, move and delete.
Promotion rule
When page-specific code starts being used on a second page, move it up to Tier 2 instead of copying it. When Tier 2 code ends up on every page, move it to Tier 1.
S1: CSS Styling
- Tier 1: Layout, navigation, footer, typography, color variables and resets go into one global stylesheet, e.g.
app.css, globals.css or style.css, depending on the framework.
- Tier 2: A short component style may live in the global stylesheet under a comment header (e.g.
/* === Modal === */). A longer or self-contained component gets its own stylesheet, e.g. modal.css, imported only where the component is used, or from the global stylesheet if it appears on most pages.
- Tier 3: Short page styles stay on the page (a
<style> block, a scoped style, or the page's CSS Module). Long page styles move into a dedicated stylesheet, e.g. pricing.css, loaded only on that page.
S2: Frontend JavaScript
- Tier 1: Behavior needed on every page (navigation toggle, theme switch, global event setup) goes into the global entry script, e.g.
app.js. Shared helpers (fetch wrapper, formatting, validation) go into small modules in a utils/ or lib/ folder and are imported where needed, not attached to window.
- Tier 2: Logic for a reusable component lives in its own module, e.g.
modal.js, exporting an init function (or a hook/composable in component frameworks) and imported only on pages that use it. Only very small shared behavior used on most pages may stay in app.js, grouped under a comment header.
- Tier 3: Short page logic stays with the page (the page component, or a small module script in the page). Long page logic moves into a dedicated file, e.g.
pages/pricing.js, loaded only on that page.
Keep structure, style and behavior apart
- Do not use inline event handlers (
onclick="...") in HTML; attach listeners in the script.
- Do not set visual styles directly from JS (
el.style.display = ...) for static states; toggle CSS classes (e.g. is-open) and define the look in CSS.
- Use
data-* attributes as JS hooks (e.g. data-modal-open) instead of styling classes, so renaming a class for design reasons never breaks behavior.
- Separate non-UI logic (API calls, data transformation, business rules) from DOM or rendering code, e.g. in
lib/ or services/, so it can be reused and tested independently.
Pair component files
When a component has both styles and behavior, keep them together, e.g. components/modal/modal.css and components/modal/modal.js, or Modal.jsx next to Modal.module.css.
Example
A website has six pages:
- All six share the same navigation bar → its styles go into
app.css, its mobile menu toggle into app.js (Tier 1).
- Three pages use a modal spanning many lines → it gets
modal.css and modal.js, imported on those three pages (Tier 2). A very small modal could instead live in the global files under a /* === Modal === */ header.
- One page has a quote block with four lines of CSS and a three-line "copy quote" button handler → both stay on that page (Tier 3, short).
- Another page has a custom animation with 80 lines of CSS and a 120-line pricing calculator → they move to
hero-animation.css and pricing.js, loaded only on that page (Tier 3, long).
Framework Notes
- React / Next.js: Import global styles once at the app root (root layout or
_app). For components and pages, prefer co-located CSS Modules such as Modal.module.css next to Modal.jsx. Extract reusable stateful logic into custom hooks (e.g. useModal) and non-UI logic into lib/. In the Next.js App Router, keep 'use client' components as small as possible and only where interactivity is needed.
- Vue: Global styles and setup in the entry file; component and page styles in
<style scoped> blocks, or a separate file for long styles. Extract reusable logic into composables (e.g. useModal).
- Laravel (Blade + Vite): Global styles and scripts in
resources/css/app.css and resources/js/app.js. Component or page files live in resources/css/ and resources/js/, must be added to the input array in vite.config.js, and are loaded with @vite(...) in the view that needs them (e.g. via @push('styles') and @push('scripts')).
- Plain HTML: Link the global stylesheet and script on every page, and additional files only on the pages that use them. Load scripts with
<script type="module" src="...">, which is deferred by default.
Avoid
- One monolithic stylesheet or script containing page-specific code for every page.
- Copy-pasting the same rules or functions into multiple page files.
- Inline
style="..." attributes for static styling and inline on* event handlers.
- Global variables on
window for sharing code between files; use module imports.
- Creating a separate file for every few lines of code, which fragments the project without benefit.
1---2name: soc-ui3description: Applies separation-of-concerns rules for organizing frontend CSS and JavaScript across a multi-page website or web app - deciding what goes into global files, into component files, or stays page-local, and keeping structure, styling and behavior apart. Use when writing, adding, or refactoring custom styles or client-side scripts in a multi-page frontend (React, Next.js, Vue, Laravel/Blade, plain HTML), when creating new pages or UI components like modals, or when the user asks where CSS or JS should live or how to split stylesheets and scripts.4---56# Separation of Concerns (SoC) for UI Styling and Scripts78## When to Use910Use this skill whenever you write or restructure custom CSS or frontend JavaScript in a project that spans multiple pages, regardless of framework. It applies when adding a new page, adding a reusable component (modal, card, quote block), or cleaning up a stylesheet or script that has grown too large.1112## Before You Start13141. Inspect the existing project structure. If the project already has a clear convention for stylesheets or scripts (e.g. CSS Modules, a `components/` folder with co-located files, a `hooks/` or `lib/` folder), follow it instead of introducing a new one.152. If the project uses a utility framework such as Tailwind, prefer utility classes and only write custom CSS for what utilities cannot express cleanly. The CSS rules below then apply to that custom CSS.1617## The Three Tiers1819Both CSS (S1) and JavaScript (S2) are sorted into the same three tiers. Never put everything into a single file by default, and never duplicate the same code across several pages.2021| Tier | Scope | Where it lives |22|------|-------|----------------|23| 1 | Global | Used on all pages | One global file (`app.css`, `app.js`, root layout) |24| 2 | Shared component | Used on several, but not all, pages | Short: global file under a clear header. Longer or self-contained: own file (`modal.css`, `modal.js`) |25| 3 | Page-specific | Used on exactly one page | Short: on the page itself. Long: own file for that page (`pricing.css`, `pricing.js`) |2627As a guideline, "short" means roughly under 30 lines. Readability of the page file matters more than an exact count. When in doubt about Tier 2, prefer a separate component file: it keeps the global file small and makes the component easy to find, move and delete.2829### Promotion rule3031When page-specific code starts being used on a second page, move it up to Tier 2 instead of copying it. When Tier 2 code ends up on every page, move it to Tier 1.3233## S1: CSS Styling3435- **Tier 1:** Layout, navigation, footer, typography, color variables and resets go into one global stylesheet, e.g. `app.css`, `globals.css` or `style.css`, depending on the framework.36- **Tier 2:** A short component style may live in the global stylesheet under a comment header (e.g. `/* === Modal === */`). A longer or self-contained component gets its own stylesheet, e.g. `modal.css`, imported only where the component is used, or from the global stylesheet if it appears on most pages.37- **Tier 3:** Short page styles stay on the page (a `<style>` block, a scoped style, or the page's CSS Module). Long page styles move into a dedicated stylesheet, e.g. `pricing.css`, loaded only on that page.3839## S2: Frontend JavaScript4041- **Tier 1:** Behavior needed on every page (navigation toggle, theme switch, global event setup) goes into the global entry script, e.g. `app.js`. Shared helpers (fetch wrapper, formatting, validation) go into small modules in a `utils/` or `lib/` folder and are imported where needed, not attached to `window`.42- **Tier 2:** Logic for a reusable component lives in its own module, e.g. `modal.js`, exporting an init function (or a hook/composable in component frameworks) and imported only on pages that use it. Only very small shared behavior used on most pages may stay in `app.js`, grouped under a comment header.43- **Tier 3:** Short page logic stays with the page (the page component, or a small module script in the page). Long page logic moves into a dedicated file, e.g. `pages/pricing.js`, loaded only on that page.4445### Keep structure, style and behavior apart4647- Do not use inline event handlers (`onclick="..."`) in HTML; attach listeners in the script.48- Do not set visual styles directly from JS (`el.style.display = ...`) for static states; toggle CSS classes (e.g. `is-open`) and define the look in CSS.49- Use `data-*` attributes as JS hooks (e.g. `data-modal-open`) instead of styling classes, so renaming a class for design reasons never breaks behavior.50- Separate non-UI logic (API calls, data transformation, business rules) from DOM or rendering code, e.g. in `lib/` or `services/`, so it can be reused and tested independently.5152### Pair component files5354When a component has both styles and behavior, keep them together, e.g. `components/modal/modal.css` and `components/modal/modal.js`, or `Modal.jsx` next to `Modal.module.css`.5556## Example5758A website has six pages:5960- All six share the same navigation bar → its styles go into `app.css`, its mobile menu toggle into `app.js` (Tier 1).61- Three pages use a modal spanning many lines → it gets `modal.css` and `modal.js`, imported on those three pages (Tier 2). A very small modal could instead live in the global files under a `/* === Modal === */` header.62- One page has a quote block with four lines of CSS and a three-line "copy quote" button handler → both stay on that page (Tier 3, short).63- Another page has a custom animation with 80 lines of CSS and a 120-line pricing calculator → they move to `hero-animation.css` and `pricing.js`, loaded only on that page (Tier 3, long).6465## Framework Notes6667- **React / Next.js:** Import global styles once at the app root (root layout or `_app`). For components and pages, prefer co-located CSS Modules such as `Modal.module.css` next to `Modal.jsx`. Extract reusable stateful logic into custom hooks (e.g. `useModal`) and non-UI logic into `lib/`. In the Next.js App Router, keep `'use client'` components as small as possible and only where interactivity is needed.68- **Vue:** Global styles and setup in the entry file; component and page styles in `<style scoped>` blocks, or a separate file for long styles. Extract reusable logic into composables (e.g. `useModal`).69- **Laravel (Blade + Vite):** Global styles and scripts in `resources/css/app.css` and `resources/js/app.js`. Component or page files live in `resources/css/` and `resources/js/`, must be added to the `input` array in `vite.config.js`, and are loaded with `@vite(...)` in the view that needs them (e.g. via `@push('styles')` and `@push('scripts')`).70- **Plain HTML:** Link the global stylesheet and script on every page, and additional files only on the pages that use them. Load scripts with `<script type="module" src="...">`, which is deferred by default.7172## Avoid7374- One monolithic stylesheet or script containing page-specific code for every page.75- Copy-pasting the same rules or functions into multiple page files.76- Inline `style="..."` attributes for static styling and inline `on*` event handlers.77- Global variables on `window` for sharing code between files; use module imports.78- Creating a separate file for every few lines of code, which fragments the project without benefit.