template-kit/no-css-import-from-render-path
The template-kit/no-css-import-from-render-path authoring rule — what it enforces, why, and how to fix it.
No local
.cssimport in a section’s render path. A package’s.cssimport belongs there — it is how its stylesheet reaches the section’s cascade layer.
An import whose specifier ends in .css, in any of the section’s .ts/.tsx
files, is reported only when the specifier is local — relative
(import "./gallery.css";) or absolute (import "/gallery.css";) — including the
side-effect-only form with no bindings.
A bare specifier (import "swiper/css";, import "@scope/pkg/dist/style.css";) is
not reported. That is the sanctioned channel for a third-party package’s stylesheet.
What it does not match:
.scssand.less— a different pipeline, out of this rule’s scope;require("./x.css");- a dynamic
import("./x.css").
There is no island exemption: a "use client" file is part of the render path too — it is
server-rendered inline before it is hydrated.
Reason
Section titled “Reason”The section’s CSS build works by bundling the render path itself — Renderer.tsx and
every module it imports, components and islands alike — and harvesting the CSS those
imports pull in. A bare specifier resolves into node_modules, which is exactly what that build
looks for: import "swiper/css"; in Renderer.tsx is not a mistake, it is the mechanism.
A local stylesheet is a different problem. Every .css file that already lives in the
section’s folder is collected on its own, by filename — the build doesn’t need an import
to find it. Importing it again from the render path doesn’t just ship duplicate rules
harmlessly: it can also smuggle a stray @layer order statement into a nested layer and
corrupt the cascade for the whole page.
Delete the import. A local stylesheet already ships — the build picks up every .css file
in the section folder on its own; importing it again only makes it double-emit.
A package’s stylesheet is different: import it by its bare specifier, from the module
that needs it. The build bundles Renderer.tsx’s import graph transitively, so a bare
specifier is equally legal in the Renderer itself or in any component or island it
imports — not only at the top of the tree. That import is how the build finds the
stylesheet and routes it into the section’s cascade layer.
Before
Section titled “Before”import { Section } from "@homepages/template-kit";
import "./styles.css"; // template-kit/no-css-import-from-render-path — already collected by filename
import type { Props } from "./schema.js";
export function Renderer({ slots }: Props) { return ( <Section> <div className="tr-gallery-track">{slots.headline}</div> </Section> );}The local import is simply deleted — styles.css was already shipping:
import { Section } from "@homepages/template-kit";
import type { Props } from "./schema.js";
export function Renderer({ slots }: Props) { return ( <Section> <div className="tr-gallery-track">{slots.headline}</div> </Section> );}Compare a package’s stylesheet, which is the legal counterpart — imported by its bare specifier, from the same file, and left in place:
import { Section } from "@homepages/template-kit";import "swiper/css"; // legal — a bare specifier is how package CSS reaches this section's layer
import type { Props } from "./schema.js";
export function Renderer({ slots }: Props) { return ( <Section> <div className="tr-gallery-track">{slots.headline}</div> </Section> );}See also
Section titled “See also”no-bare-css-import— the other half of this mental model. Render path: no local CSS imports. Hand-written CSS: no@imports at all, package or otherwise. An author who reads only one of the two pages will be confused by the other.- Theme and CSS — the two CSS entries and how they load.