A web component for block and inline content warnings.
Based on the original concept by Aaron Gustafson.
Additional demos:
npm install @aarongustafson/content-warning
Import the package to automatically define the <content-warning> custom element:
import '@aarongustafson/content-warning';
Or use the define-only script in HTML:
<script
src="./node_modules/@aarongustafson/content-warning/define.js"
type="module"
></script>
Import the class and define the custom element with your preferred tag name:
import { ContentWarningElement } from '@aarongustafson/content-warning/content-warning.js';
customElements.define('my-custom-name', ContentWarningElement);
<!-- Block content warning -->
<content-warning type="violence spoilers">
<p>This content contains violence and spoilers for the series finale.</p>
</content-warning>
<!-- Inline content warning -->
<p>
The character dies in
<content-warning type="spoilers" inline>episode 5</content-warning>.
</p>
Important: Reader Mode parsers (Edge Immersive Reader, Safari Reader, etc.) extract content before JavaScript runs. To prevent Reader Mode from extracting content inside <content-warning> elements, add the hidden attribute:
<content-warning type="violence" hidden>
<p>This content will be hidden from Reader Mode.</p>
</content-warning>
How it works:
hidden attribute hides the element → Reader Mode skips it entirelyhidden attributeWithout the hidden attribute, Reader Mode will extract and display the content even though it appears hidden in the normal page view.
Note: The hidden attribute is automatically removed when the component boots, so you don't need to manage it yourself.
The component offers two modes for hiding content, each with different trade-offs:
Reader Mode Safe - Content is truly hidden from Reader Mode and screen readers until revealed.
<content-warning type="sensitive content">
<p>This content is completely hidden until revealed.</p>
</content-warning>
hidden and inert attributes on the content wrapperVisual Only - Content is visually obscured but still present in the DOM.
<content-warning type="spoilers" blur>
<p>This content is blurred but technically visible in the DOM.</p>
</content-warning>
filter: blur() to obscure content visuallyaria-hidden="true" to hide from screen readers--content-warning-blur-amount CSS propertyTrade-off Summary:
blur: Maximum safety and hiding (recommended for sensitive content)blur: Visual obscuring effect (not fully hidden from all contexts)| Attribute | Type | Default | Description |
|---|---|---|---|
type |
string |
"content" |
Space-separated list of warning types (e.g., "violence spoilers nsfw") |
label-prefix |
string |
"Content Warning" |
The prefix text for the warning label |
label-suffix |
string |
"Click to reveal" |
The suffix text for the warning label. Set to "false" to hide. |
inline |
boolean |
false |
Display the warning inline instead of as a block overlay |
blur |
boolean |
false |
Use blur visual effect instead of complete hiding (NOT Reader Mode safe) |
Default Button Label Format: {prefix}: {type} {suffix}
Example: "Content Warning: violence spoilers Click to reveal"
Note: Punctuation and spacing between label parts are controlled via CSS pseudo-elements (:after and :before), making them easy to customize without affecting the underlying text content.
The component fires custom events that you can listen to:
| Event | Description | Detail |
|---|---|---|
content-warning:revealed |
Fired when the user reveals the content by clicking the button | { type: string } - The type of content warning |
const element = document.querySelector('content-warning');
element.addEventListener('content-warning:revealed', (event) => {
console.log('Content revealed:', event.detail.type);
// Track analytics, log user action, etc.
});
| Property | Type | Description |
|---|---|---|
type |
string |
Get/set the warning type(s) |
labelPrefix |
string |
Get/set the prefix text for the warning label |
labelSuffix |
string |
Get/set the suffix text for the warning label |
revealed |
boolean (read-only) |
Whether the content has been revealed |
Customize the component's appearance with CSS variables:
| Property | Default | Description |
|---|---|---|
--content-warning-color |
#fff |
Outline color for focus state |
--content-warning-blur-amount |
10px |
Amount of blur in blur mode (e.g., 5px, 20px) |
content-warning {
--content-warning-color: #ff6b6b;
--content-warning-blur-amount: 15px;
}
You can style internal elements using CSS Shadow Parts:
| Part | Description |
|---|---|
overlay |
The full-area overlay div that covers the content |
button |
The warning button element inside the overlay |
label-prefix |
The prefix text span (e.g., "Content Warning") |
/* Style the full-area overlay */
content-warning::part(overlay) {
background: rgba(139, 0, 0, 0.95);
}
/* Style the button inside the overlay */
content-warning::part(button) {
color: #fff;
border: 3px solid #ff6b6b;
padding: 2rem;
font-size: 1.25rem;
text-transform: uppercase;
letter-spacing: 0.1em;
background: rgba(0, 0, 0, 0.5);
border-radius: 0.5rem;
}
/* Style individual label parts */
content-warning::part(label-prefix) {
font-weight: bold;
}
content-warning::part(label-type) {
font-style: italic;
color: #ff6b6b;
}
content-warning::part(label-suffix) {
font-size: 0.875em;
opacity: 0.9;
}
/* Style inline warnings differently */
content-warning[inline]::part(overlay) {
background: #333;
}
content-warning[inline]::part(button) {
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
}
Customize the button label for different languages using label-prefix and label-suffix attributes:
<!-- Spanish -->
<content-warning
type="violencia gore"
label-prefix="Advertencia de Contenido"
label-suffix="Haz clic para revelar"
>
<img src="image.jpg" alt="Sensitive image" />
</content-warning>
<!-- French -->
<content-warning
type="contenu sensible"
label-prefix="Avertissement"
label-suffix="Cliquez pour révéler"
>
<p>Contenu en français...</p>
</content-warning>
<!-- No suffix -->
<content-warning type="graphic content" label-suffix="false">
<p>Content without suffix text</p>
</content-warning>
Style each label part individually:
content-warning::part(label-prefix) {
font-weight: bold;
}
content-warning::part(label-type) {
font-style: italic;
color: #ff6b6b;
}
content-warning::part(label-suffix) {
font-size: 0.875em;
opacity: 0.9;
}
```
### Customizing Punctuation
Punctuation and spacing between label parts are controlled via CSS pseudo-elements:
```css
/* Default punctuation (already applied) */
.label-prefix::after {
content: ": "; /* Colon and space after prefix */
}
[part="label-type"]::before {
content: " "; /* Space before type */
}
[part="label-suffix"]::before {
content: " "; /* Space before suffix */
}
/* Customize punctuation */
content-warning::part(button) .label-prefix::after {
content: " — "; /* Em dash instead of colon */
}
/* Remove punctuation entirely */
content-warning::part(button) .label-prefix::after {
content: " "; /* Just a space */
}
```adding: 0.5rem 0.75rem;
border-radius: 0.25rem;
}
The component follows accessibility best practices:
<button> element for the warning interactionaria-hidden or hidden attribute)hidden + inert attributes (Reader Mode safe)aria-hidden="true" (visual obscuring only)role="alert" on the host element when content is revealedrole="alert" region for screen reader announcementThis component uses modern web standards:
For older browsers, you may need polyfills.
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Lint code
npm run lint
# Format code
npm run format
# View demo
open demo/index.html
MIT © Aaron Gustafson