skip to content

Styles and Assets

A component mounted on its own looks wrong until the application's global CSS is imported in the support file and the dev server compiles it the same way the real build does.

on this pageshow

explore

questions

5

Why does a component mounted by Cypress's cy.mount() render unstyled?

level: juniorimportance: must knowfreq 74%

answer

  1. The mount page is not your app
  2. Startup code never ran
  3. Two hooks rebuild the environment
  4. Support file runs before every spec
  5. Import main.css in cypress/support/component.js

basics

~20 s

Cypress renders components into cypress/support/component-index.html, not your application's page, so nothing the app's entry file does at startup has run. Import your global stylesheets in cypress/support/component.js, or add the same link tags to that index file.

solid answer

~40 s

A Cypress component test never loads your application's `index.html`. The dev server serves `cypress/support/component-index.html` — a bare page with a root element — and the component is mounted into it. Everything the app does before its first component renders (importing `main.css`, a Tailwind build, a normalize sheet, a `<link>` to a font) has not happened, so a rating widget or toast falls back to browser defaults. Cypress gives you two hooks to put that environment back: import the stylesheets in `cypress/support/component.js`, which is compiled and run before every spec, and copy the head content of the real `index.html` into `cypress/support/component-index.html`. The rule is to mirror where your application does it — what the entry file imports goes in the support file, what sits in the app's `<head>` goes in the index file.

code

javascript · 4 lines
javascript
// cypress/support/component.js - mirrors src/main.js
import 'normalize.css'
import '../../src/styles/tokens.css'
import '../../src/styles/main.css'

go deeper

for a junior

Be ready to say where a Cypress component test's global CSS goes and why a mounted component starts with none of it. Naming cypress/support/component.js is most of the answer.

for a middle

Explain the two hooks and which part of the application each one stands in for: the support file for the entry file, the index HTML for the app's own page.

for a senior

Connect a missing stylesheet to failing actionability assertions, and show how you factor the shared startup into one module that both the app and the support file import.

for a principal

Own the position that the harness reproduces the real environment rather than an approximation of it, and be able to say what a divergence between the two costs the suite's credibility.

## What a Cypress component test actually renders Cypress component testing never visits your application. When a run starts, Cypress boots the dev server named by `component.devServer`, and for each spec it loads a small page of its own — `cypress/support/component-index.html` by default — imports your support file, imports the spec, and lets `cy.mount()` put the component into that page. That page is deliberately bare. The scaffolded version carries a charset meta tag, a viewport meta tag, a `<title>` and a single root element, and nothing else. It is not your application's `index.html`, it has never executed your `main.js`, and no ancestor component of the thing under test has rendered. ## Why the rating widget looks wrong Almost every application does a pile of one-time work before its first component appears, and CSS is most of it: - The entry file (`main.js`, `main.ts`, `index.jsx`) imports `./main.css`, a normalize sheet, or a compiled Tailwind build. - The application's `index.html` head carries `<link>` tags for a font, an icon font or a vendor theme, or an `@import` rule inside a `<style>` block. - A root component such as `App.vue` or `App.svelte` embeds global rules — `#app { font-family: ... }` — that every descendant silently assumes. None of that happens in a component spec. So a rating widget draws its stars at the browser default size, a toast loses the `position: fixed` and stacking context its overlay depends on, and a data grid's sticky header collapses into the rows. The markup is correct; the cascade the markup was written against is simply absent. This matters well beyond appearance. Cypress runs in a real browser with a real box model, and commands such as `.click()` and assertions such as `.should('be.visible')` consult width, height, overflow and occlusion before they pass. An unstyled component therefore fails **behavioural** assertions too, and those failures are actively misleading: they name the component when the cause is a stylesheet that never loaded. ## The two hooks Cypress gives you | Hook | When it loads | Mirror it against | |---|---|---| | `cypress/support/component.js` | Compiled and executed before every spec in the run | The application's entry file (`main.js` / `index.jsx`) | | `cypress/support/component-index.html` | The page the component is mounted into | The application's `index.html` | The rule that keeps this maintainable fits in one sentence: **do it where your application does it.** If a stylesheet reaches the browser because the entry file says `import './main.css'`, import it in the support file. If it reaches the browser because the app's `<head>` carries a `<link>`, put the same `<link>` in the index file. Faithfully reproducing the real environment is the entire point of the exercise; guessing produces a harness that drifts. Two details are worth knowing about the support file. It is compiled and bundled by the same dev server that compiles your specs, so it can `import` exactly what your source can — a `.css` file, a `.scss` file, a design-token module. And it is optional: setting `supportFile` to `false` turns it off entirely, which is occasionally done for speed and almost always regretted, because that is the file the styles were living in. ## Do it once, not twice Duplicating the import list in two places guarantees the two lists diverge. The pattern to reach for is a single shared startup module imported from both entry points: 1. Create `src/setup.js` and move the global stylesheet imports into it, along with anything else the app builds once at startup. 2. Have `main.js` import that module and mount the real application. 3. Have `cypress/support/component.js` import the same module — often a single line. Adding a stylesheet to the application now adds it to the harness for free, and there is nothing left to keep in sync by hand. ## When it is still wrong Work through these in order before touching the component: - **Did the rule load at all?** Inspect the element's computed styles. A class that is present on the element but contributes nothing means the sheet never arrived. - **Did a file 404?** A `@font-face` or a background image referenced by URL has to be served by the dev server Cypress started, which is not the server that serves your app in development. - **Are the styles component-scoped already?** A codebase written entirely with CSS Modules or scoped single-file-component styles needs far less of this, because those styles travel with the component through the bundler. - **Does the rule assume an ancestor?** Something written as `#app .toast` needs that ancestor to exist in the mount page, or in whatever wrapper the component is mounted through. - **Does the theme live on the root element?** Applications commonly set a `data-theme` attribute or a `dark` class on `<html>` or `<body>` at startup. The mount page has neither, so a design system whose tokens are defined under a theme selector resolves every custom property to its fallback. The last one is the reason a component can look *almost* right — correct spacing, wrong colours — and it is worth checking early, because it looks nothing like a missing-stylesheet failure.

  • A component library uses only CSS Modules and scoped styles. Does any of this still apply?
    Much less of it. Styles that travel with the component through the bundler arrive with the mount, so those components look right immediately. You still usually need what lives outside components: resets, custom-property definitions, font declarations. A token sheet defining `--space-2` is global even in a fully scoped codebase, and any rule written against an ancestor selector still needs that ancestor.
  • Why can an unstyled mount fail a Cypress assertion rather than merely look wrong?
    Cypress runs in a real browser and its actionability and visibility checks read the real box model — width, height, overflow, and whether something covers the element. Strip the layout CSS and a toast can compute to zero height or sit behind an overlay, so `.should('be.visible')` and `.click()` fail. The failure names the component; the cause is the missing stylesheet.

Mounting a component on its own is like standing a stage prop under the house lights: the prop is exactly right, but the set, backdrop and lighting rig the scene was designed around are all still in storage.

saying these in an interview costs you the question

  • Says Cypress injects the application's stylesheets automatically
  • Blames the component and starts rewriting its CSS
  • Thinks a mounted component renders inside the real application page
  • Adds inline styles to the spec instead of importing the global sheet
open as a page

What does Cypress's component.devServer framework and bundler pair actually do?

level: middleimportance: must knowfreq 66%

basics

~20 s

The framework and bundler pair tells Cypress which dev server to start and how to compile your specs. Cypress boots Vite or webpack on a free port, reuses your project's own bundler config file, and serves the compiled support file and spec.

open as a page

Adding viteConfig to Cypress's component.devServer broke your aliases. Why?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Passing viteConfig or webpackConfig replaces Cypress's automatic detection of your project's bundler config. Cypress then compiles specs with only what you passed, so plugins and aliases declared in the real config file disappear. Import that file and spread it instead.

open as a page

How much of your real build should a Cypress component harness reuse?

level: principalimportance: should knowfreq 36%

basics

~20 s

Reuse the application's real bundler config by default. Every difference between the harness build and the real build is a class of defect the component suite cannot see, so keep divergence to a short, named list you can justify and re-check elsewhere.

open as a page