The 1 file this component needs and nothing else, each fenced with the path it belongs at, followed by what the library's own verification covered and what it did not. No instructions attached — for placing it yourself, or handing it to something that has its own.
Read it first
Install kit: "Dusk glow"
Library page: https://astro.baysixmedia.com/components/dusk-glow/
Files: 1 required, 1 supporting
Write each block below to the path named in its header, relative to the project root. A block marked (required) is one the component does not work without; (supporting) is documentation. Nothing here needs downloading except a block that says so in as many words.
===== FILE (required): src/components/DuskGlow.astro =====
---
/**
* Dusk glow
*
* A dark dome with a coloured glow rising around it: one elliptical radial
* gradient, wider and taller than its box, centred high so the flat core
* fills the top and the colour is pushed out to the bottom corners.
*
* The whole thing is a single `background-image`. There is no script, no
* canvas, no image, no extra element and nothing to animate — which is worth
* saying plainly, because a backdrop that costs one paint is the reason to
* reach for this one over a field or a mesh.
*
* Every number in it is a prop rather than a constant, so the same component
* is a midnight violet, a warm sunrise or a cold teal without a second copy
* of the file.
*
* Self-contained: no imports, no packages, no global stylesheet, no webfont.
* Every colour and measurement is a `--dg-*` custom property on the root.
*/
interface Props {
/**
* The flat centre of the dome, and the colour the content sits on.
* Defaults to black, which is what makes the glow read as light rather
* than as a second colour.
*/
core?: string;
/** The colour the gradient reaches at the far edge of the ellipse. */
glow?: string;
/**
* Text colour for whatever is in the slot.
*
* A backdrop that paints itself near-black owes its content a colour: a
* host's default body text is dark, and dark on this is nothing at all.
* It is a prop rather than a constant because `core` is one too — a pale
* core wants dark ink. Only `"section"` mode has content to colour.
*/
ink?: string;
/**
* How much of the ellipse the core holds before the glow starts.
*
* The single most expressive value here. Low and the colour climbs most
* of the box; high and it is a rim around the bottom corners. The
* reference sits at 40%.
*/
coreStop?: string;
/**
* The ellipse's radii, as a share of the box. Over 100% on purpose: the
* gradient is bigger than what you can see, so the visible part is the
* inner slope of a much larger dome rather than a circle with an edge.
*/
radiusX?: string;
radiusY?: string;
/**
* Where the ellipse is centred. The vertical value is what tilts the
* whole effect — above the middle and the glow gathers along the bottom,
* below it and the dome inverts into a sunrise.
*/
originX?: string;
originY?: string;
/**
* How it sits in the page.
*
* `"section"` is a block in the flow that holds its own slot content — a
* hero with the glow behind it. `"layer"` is an absolutely positioned
* fill of the nearest positioned ancestor, for dropping behind content
* that already exists; the slot is ignored. `"page"` pins it to the
* viewport behind everything, which is the whole-page backdrop, and the
* slot is ignored there too.
*/
mode?: "section" | "layer" | "page";
/**
* Height floor in `"section"` mode when the slot gives it none.
*
* Full screen by default, because the gradient is sized as a share of its
* own box and a short box is a thin slice of the dome rather than the
* dome. `svh` rather than `vh` so a phone's collapsing address bar does
* not leave a strip of page showing under it.
*/
minHeight?: string;
/** Added to the root. */
class?: string;
/** Root element id. */
id?: string;
}
const {
core = "#000000",
glow = "#6633ee",
ink = "#ffffff",
coreStop = "40%",
radiusX = "125%",
radiusY = "125%",
originX = "50%",
originY = "10%",
mode = "section",
minHeight = "100svh",
class: className,
id,
} = Astro.props;
/*
* Only the three the stylesheet knows what to do with. An unrecognised value
* lands on "section" rather than on a class that matches nothing, which would
* render an unstyled box rather than a backdrop.
*/
const layout = mode === "page" || mode === "layer" ? mode : "section";
/*
* The gradient is assembled here rather than in the stylesheet so the whole
* shape is one readable line, and so a consumer overriding `--dg-core` alone
* does not have to restate the other six values to keep it.
*/
const style = [
`--dg-core:${core}`,
`--dg-glow:${glow}`,
`--dg-ink:${ink}`,
`--dg-core-stop:${coreStop}`,
`--dg-radius-x:${radiusX}`,
`--dg-radius-y:${radiusY}`,
`--dg-origin-x:${originX}`,
`--dg-origin-y:${originY}`,
`--dg-min-height:${minHeight}`,
].join(";");
---
<div
class:list={["dusk-glow", `dusk-glow--${layout}`, className]}
id={id}
style={style}
data-dusk-glow
data-dg-mode={layout}
{...(layout === "section" ? {} : { "aria-hidden": "true" })}
>
{/*
Only the block that holds content gets a slot. In the other two modes
the element is scenery — out of the accessibility tree and out of the
way of the pointer — and there is nothing for content to be inside.
*/}
{layout === "section" && <slot />}
</div>
<style>
.dusk-glow {
box-sizing: border-box;
/*
* The component, in one declaration.
*
* `background-color` under it as well as the gradient: a gradient is
* an image, and an image is what a printer, a very old engine or a
* blocked resource can end up without. The core colour underneath
* means the worst case is a flat dark panel rather than a white one
* with light text on it.
*/
background-color: var(--dg-core);
background-image: radial-gradient(
var(--dg-radius-x) var(--dg-radius-y) at var(--dg-origin-x) var(--dg-origin-y),
var(--dg-core) var(--dg-core-stop),
var(--dg-glow) 100%
);
}
.dusk-glow--section {
position: relative;
min-block-size: var(--dg-min-height);
/*
* Inherited by the slot rather than imposed on it: a host rule that
* sets a colour on its own heading still wins, and anything that says
* nothing gets something readable instead of the page's dark default
* on a near-black ground.
*
* Only this mode. The other two hold no content, and setting a colour
* on an element with no children is a claim about nothing.
*/
color: var(--dg-ink);
}
/*
* Behind content that already exists. It fills the nearest positioned
* ancestor, which is the host's job to provide — without one it will
* climb to the nearest one it finds, or to the viewport.
*
* `z-index: 0` rather than a negative: this one is a sibling of the
* content it sits behind, and a negative index would put it behind the
* host's own background as well, where a painted background would hide
* it completely.
*/
.dusk-glow--layer {
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
}
/*
* The whole-page backdrop. Fixed to the viewport and behind everything,
* which is the one job a background has.
*
* `z-index: -1` rather than a positive index under the content: it puts
* the glow behind the page's normal flow without the page having to know
* it is there. The cost is that a host which makes `body` a stacking
* context — a transform, a filter, `isolation` — will trap it, and that a
* `body` with its own opaque background will cover it. Both are in the
* README rather than worked around.
*/
.dusk-glow--page {
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
}
/*
* No reduced-motion rule, and that is a statement rather than an
* omission: there is no animation, no transition and no transform in
* this file, so there is nothing for the preference to reduce.
*/
@media (forced-colors: active) {
/*
* A gradient is a `background-image`, and forced colours do not
* replace those — so without this the purple would survive into a
* palette chosen to get rid of it, under text that has been forced to
* `CanvasText` and may now be unreadable on it. The backdrop steps
* aside instead.
*/
.dusk-glow {
background-image: none;
background-color: Canvas;
}
/* The ink goes with it. A colour chosen to sit on the glow is not the
colour to sit on `Canvas`, and here it would be the one thing still
fighting the palette the reader asked for. */
.dusk-glow--section {
color: CanvasText;
}
}
@media print {
/* Same reasoning, different reason: a full-bleed dark gradient is a
page of ink. The slot colour has to come back with it, or the words
print white on white. */
.dusk-glow {
background-image: none;
background-color: transparent;
}
.dusk-glow--section {
color: inherit;
}
}
</style>
===== END src/components/DuskGlow.astro =====
===== FILE (supporting): src/components/DuskGlow.md =====
# Dusk glow
A dark dome with a coloured glow rising around it: one elliptical radial
gradient, wider and taller than its box, centred high so the flat core fills
the top and the colour is pushed out to the bottom corners.
Catalog page: `/components/dusk-glow`
## Purpose
For a hero, a section or a whole page that needs a ground with some depth in
it and nothing else — no field, no mesh, no noise, no canvas.
The whole component is **one `background-image`**. That is the reason to reach
for it over anything animated: it costs a single paint, it has nothing to
boot, nothing to tear down, and nothing a client-side router can strand. If
you want the ground to move, this is the wrong component.
Use something else if the glow needs to be a separate colour *over* an
existing background — this paints its own ground, and `core` is that ground.
## Files required
**`DuskGlow.astro`** — one file, the whole component.
- No imports at all, not even a type.
- No npm packages, no Tailwind, no CSS reset, no icon library.
- **No script.** Nothing to boot and nothing to clean up.
- No global stylesheet. Every colour and measurement is declared on the
component's own root element as a `--dg-*` custom property.
- No webfont and no image, so the component makes **no network request of any
kind**.
- The only assumption about the host is that Astro compiles `.astro` files.
`DuskGlow.md` — this file. Documentation, not required to run.
## Basic usage
```astro
---
import DuskGlow from "../components/DuskGlow.astro";
---
<DuskGlow>
<h1>Ship it before Friday</h1>
<p>Everything else is a detail.</p>
</DuskGlow>
```
No client directive, no wrapper, no init call. It fills the screen by default
and your content sits on it.
## The three modes
| `mode` | What it is |
| :--- | :--- |
| `"section"` *(default)* | A block in the flow that **holds** its slot content. A hero with the glow behind it. |
| `"layer"` | `position: absolute; inset: 0` — fills the nearest positioned ancestor, behind content that already exists. The slot is ignored. |
| `"page"` | `position: fixed; inset: 0; z-index: -1` — the whole-page backdrop. The slot is ignored. |
`"layer"` is the shape the reference is. Use it when you already have a
positioned hero and just want a ground inside it:
```astro
<div class="relative">
<DuskGlow mode="layer" />
<div class="relative">…your existing content…</div>
</div>
```
**`"layer"` needs a positioned ancestor.** Without one it will climb to the
nearest one it finds, or to the viewport — that is how `position: absolute`
works, and the component cannot detect it for you.
**`"page"` has two ways to go wrong in a host**, both of them properties of
`z-index: -1` rather than of this component:
- a `body` (or any ancestor) that makes itself a stacking context — a
`transform`, a `filter`, `isolation`, `opacity` under 1 — traps it, and you
will see nothing;
- a `body` with its own opaque `background` paints over it.
Both are worth knowing before you debug them. `"section"` has neither problem.
## Props
Everything has a default, and the defaults are the reference.
| Prop | Type | Default | What it does |
| :--- | :--- | :--- | :--- |
| `core` | `string?` | `"#000000"` | The flat centre of the dome, and the ground the content sits on. |
| `glow` | `string?` | `"#6633ee"` | The colour at the far edge of the ellipse. |
| `ink` | `string?` | `"#ffffff"` | Text colour for slot content. `"section"` mode only. |
| `coreStop` | `string?` | `"40%"` | How much of the ellipse the core holds before the glow starts. |
| `radiusX` | `string?` | `"125%"` | The ellipse's horizontal radius, as a share of the box. |
| `radiusY` | `string?` | `"125%"` | The vertical radius. |
| `originX` | `string?` | `"50%"` | Where the ellipse is centred, horizontally. |
| `originY` | `string?` | `"10%"` | Vertically. This is what tilts the whole effect. |
| `mode` | `"section" \| "layer" \| "page"?` | `"section"` | See above. An unrecognised value falls back to `"section"`. |
| `minHeight` | `string?` | `"100svh"` | Height floor in `"section"` mode when the slot gives it none. |
| `class` | `string?` | — | Added to the root element. |
| `id` | `string?` | — | Root element id. |
## Tuning it
The four values worth playing with, in the order they change the most:
- **`coreStop`** — low and the colour climbs most of the box; high and it is a
rim around the bottom corners. Everything between 20% and 60% is usable.
- **`originY`** — above the middle and the glow gathers along the bottom;
below it and the dome inverts into a sunrise coming up from the floor.
- **`radiusY`** — under 100% tightens the dome into a visible arc; well over
it flattens the whole thing toward a linear fade.
- **`glow`** — the one that changes the mood rather than the shape.
A warm sunrise, for example:
```astro
<DuskGlow
core="#0b1d2a"
glow="#f2a65a"
ink="#0b1d2a"
coreStop="28%"
radiusX="160%"
radiusY="95%"
originX="30%"
originY="88%"
/>
```
The radii are over 100% on purpose. The gradient is bigger than what you can
see, so the visible part is the inner slope of a much larger dome rather than
a circle with an edge in the middle of your page.
## Height
`minHeight` is `100svh` rather than a fraction of the viewport, because the
ellipse is sized as a **share of its own box**: a short box is a thin slice of
the dome rather than the dome. `svh` rather than `vh` so a phone with a
collapsing address bar does not leave a strip of page showing underneath.
A shorter hero should pass its own value *and* expect to re-tune `radiusY`
and `coreStop` — the shape is proportional, so halving the height halves the
dome rather than cropping it.
## Layout
The component does not lay out your content. It is a ground, not a grid: no
padding, no centring, no max-width. Wrap what goes in the slot the way you
would wrap it on any other section.
That is a deliberate difference from the reference, whose layer carries
`px-5 py-24` and `items-center` — values that do nothing there, because the
layer has no children.
## Accessibility and preferences
- In `"layer"` and `"page"` modes the root is `aria-hidden` and takes no
pointer events: it is scenery, and there is nothing inside it.
- `ink` exists so the default is legible. A dark backdrop under a host's
default dark body text is invisible content, and the component knows what
colour its own ground is.
- **No reduced-motion rule, because there is nothing to reduce.** No
animation, no transition, no transform anywhere in the file.
- `forced-colors: active` drops the gradient and falls back to `Canvas` and
`CanvasText`. A gradient is a `background-image`, and forced colours do not
replace those — so without this the purple would survive into a palette
chosen to get rid of it, underneath text that has been forced to
`CanvasText` and may now be unreadable on it.
- `@media print` drops it too, and returns the slot colour to inherited. A
full-bleed dark gradient is a page of ink, and white text on white paper is
nothing at all.
- A `background-color` sits under the gradient as well. A gradient is an
image, and an image is what a very old engine or a blocked resource can end
up without; the fallback is a flat dark panel rather than a white one with
light text on it.
## Known differences from the reference
- **The reference is only the `"layer"` form** — one absolutely positioned
`div` with the gradient on it. `"section"` and `"page"` are this
component's.
- **Every value is a prop.** The reference hard-codes all seven inside one
Tailwind arbitrary-property class.
- **`ink`, the forced-colors rule, the print rule and the
`background-color` fallback are this component's.** The reference has none
of them.
- The gradient itself is not a difference. Rendered at 1440 × 900 against the
reference, the two are identical pixel for pixel — see the verification
record on the catalog page.
===== END src/components/DuskGlow.md =====
--- VERIFIED IN THE COMPONENT LIBRARY ---
Production-ready in the library.
Verified 2026-09-19 at 1920, 1440, 768, 320px.
Passed: full-page context, keyboard, no javascript, reduced motion, two instances, no overflow, declarations corroborated, lifecycle.
Library source fingerprint: sha256:60a6c899dc04392cd0cbe530753bd1d068202e2378177797e3327f851cde20b5 — MATCHES the files below.
--- NOT PROVEN ---
- ClientRouter: not applicable — this component ships no script.
--- WHAT THIS DOES NOT CERTIFY ---
Verification was performed on the unmodified component, in an isolated fixture, in the
library. It says nothing about this project's CSS, routes, layout or data, and nothing
about the component once you adapt it. A verified component is a good starting point,
not a guarantee about the installation you are about to build.
--- TEST IN THIS PROJECT AFTER INSTALLING OR CUSTOMISING ---
- Horizontal overflow at 1440, 1280, 1024, 834, 390 and 320px.
- Keyboard operation end to end, including visible focus.
- The no-JavaScript render.
- prefers-reduced-motion: reduce.
- Two instances on one page, if this project renders more than one.
- If this project uses <ClientRouter />: teardown and reinitialisation across a real navigation.