Skip to content

Components

remCSS components use four modern CSS techniques for isolation and intelligence:

  1. @scope — prevents styles leaking out of the component boundary (progressive enhancement)
  2. @container — components respond to their parent size, not the viewport
  3. :has() — relational state (error fields, icon buttons, loading states)
  4. light-dark() — single color definition handles both themes automatically

Buttons

html
<!-- Primary (default) -->
<button class="button">Save Changes</button>

<!-- Outline variant -->
<button class="button button-outline">Cancel</button>

<!-- Ghost variant -->
<button class="button button-ghost">Learn more</button>

<!-- Danger -->
<button class="button button-danger">Delete</button>

<!-- Sizes -->
<button class="button button-sm">Small</button>
<button class="button button-lg">Large</button>

<!-- Disabled (both methods work) -->
<button class="button" disabled>Disabled</button>
<button class="button" aria-disabled="true">Disabled</button>

<!-- Loading (aria-busy) -->
<button class="button" aria-busy="true">Loading…</button>

<!-- Full width -->
<button class="button button-block">Full Width</button>

Button Group

html
<div class="button-group">
	<button class="button button-outline">Left</button>
	<button class="button button-outline">Center</button>
	<button class="button button-outline">Right</button>
</div>

Forms

html
<form class="form">
	<!-- Text input -->
	<div class="field">
		<label class="label" for="name">
			Full name <span class="required" aria-hidden="true">*</span>
		</label>
		<input class="input" type="text" id="name" required placeholder="Jane Smith" />
		<span class="field-hint">As it appears on your ID.</span>
		<span class="field-error" aria-live="polite">Please enter your full name.</span>
	</div>

	<!-- Select -->
	<div class="field">
		<label class="label" for="country">Country</label>
		<select id="country">
			<option value="">Choose a country…</option>
			<option value="de">Germany</option>
			<option value="us">United States</option>
		</select>
	</div>

	<!-- Textarea -->
	<div class="field">
		<label class="label" for="message">Message</label>
		<textarea id="message" rows="4"></textarea>
	</div>

	<!-- Checkbox group -->
	<div class="field">
		<span class="label">Notifications</span>
		<div class="check-group">
			<label class="check-label"> <input type="checkbox" checked /> Email </label>
			<label class="check-label"> <input type="checkbox" /> SMS </label>
		</div>
	</div>

	<button class="button" type="submit">Submit</button>
</form>

Error States

Error styles are applied automatically using :has(:invalid:not(:placeholder-shown)) — no JavaScript required:

html
<div class="field">
	<label class="label" for="email">Email</label>
	<!-- When this input is invalid and has been interacted with,
       the parent .field gets --field-border-color: var(--color-danger) -->
	<input type="email" id="email" required value="not-an-email" />
	<span class="field-error">Please enter a valid email address.</span>
</div>

Tables

Wrap tables in .table-wrapper for horizontal scroll and rounded corners:

html
<div class="table-wrapper">
	<table class="table table-striped table-hover">
		<caption>
			Q1 2026 Sales by Region
		</caption>
		<thead>
			<tr>
				<th scope="col">Region</th>
				<th scope="col" aria-sort="descending">Revenue</th>
				<th scope="col" data-numeric>Units</th>
				<th scope="col">Status</th>
			</tr>
		</thead>
		<tbody>
			<tr>
				<td>Europe</td>
				<td data-numeric>€1,284,000</td>
				<td data-numeric>3,420</td>
				<td data-status="success">On track</td>
			</tr>
			<tr>
				<td>North America</td>
				<td data-numeric>$980,000</td>
				<td data-numeric>2,100</td>
				<td data-status="warning">At risk</td>
			</tr>
		</tbody>
	</table>
</div>

Responsive Stacking

Add .table-responsive for column-to-card stacking on narrow containers. Use data-label attributes to provide column headers in the stacked view:

html
<div class="table-wrapper">
	<table class="table table-responsive">
		<thead>
			<tr>
				<th>Name</th>
				<th>Amount</th>
			</tr>
		</thead>
		<tbody>
			<tr>
				<td data-label="Name">Jane Smith</td>
				<td data-label="Amount" data-numeric>€420</td>
			</tr>
		</tbody>
	</table>
</div>

Typography

Headings, paragraphs, and text elements are styled automatically — no class required.

html
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>

<p>
	Body paragraph text. Max line length is constrained to <code>--measure</code> (66ch) automatically
	for optimal readability. Text wrapping uses <code>text-wrap: pretty</code>
	to avoid orphaned words.
</p>

<!-- Lead paragraph — slightly larger -->
<p class="lead">An introductory paragraph with <code>--text-lg</code> size and wider measure.</p>

Font weight classes

html
<p class="font-light">Light (300)</p>
<p class="font-medium">Medium (400)</p>
<p class="font-bold">Bold (700)</p>

Text alignment

html
<p class="text-start">Start-aligned (RTL-aware)</p>
<p class="text-center">Centered</p>
<p class="text-end">End-aligned (RTL-aware)</p>

Blockquotes & Lists

Blockquote

html
<blockquote>
	<p>
		The Golden Ratio is a reminder that mathematics and beauty are not separate domains — they are
		the same domain.
	</p>
	<footer>— Luca Pacioli, <cite>De Divina Proportione</cite></footer>
</blockquote>

Blockquotes receive a left accent border in --color-accent (orange) and italic styling automatically.

Unordered List

html
<ul>
	<li>Golden Ratio scale</li>
	<li>rem-only units</li>
	<li>oklch() colors</li>
</ul>

Ordered List

html
<ol>
	<li>Install: <code>npm install @dstn/remcss</code></li>
	<li>Link the stylesheet</li>
	<li>Use the classes</li>
</ol>

Definition List

html
<dl>
	<dt>φ (phi)</dt>
	<dd>The Golden Ratio — approximately 1.618033988749895</dd>

	<dt>rem</dt>
	<dd>Root-relative em — the unit everything in remCSS is built on</dd>

	<dt>oklch()</dt>
	<dd>A perceptually uniform color space used for all color values</dd>
</dl>

Unstyled List

Add role="list" to remove list markers when using a list purely for semantics:

html
<ul role="list" class="flex gap-1 flex-wrap">
	<li><a href="#">Home</a></li>
	<li><a href="#">About</a></li>
	<li><a href="#">Contact</a></li>
</ul>

Code & Pre

Inline Code

html
<p>Use <code>var(--step-1)</code> for spacing at the base GR step.</p>
<p>Press <kbd>Ctrl</kbd> + <kbd>K</kbd> to open search.</p>
<p>The function returns <samp>true</samp> on success.</p>

<code>, <kbd>, and <samp> all receive the monospace font stack, a subtle background, and padding automatically.

Preformatted / Code Block

html
<pre><code>@layer base, components, utilities;

:root {
  --phi: 1.618033988749895;
  --step-1: calc(1rem * var(--phi));
}</code></pre>

<pre> receives a left accent border in the brand color and horizontal scroll on overflow.

Inline Elements

These elements are styled automatically with no class needed:

html
<p>
	<strong>Bold/strong text</strong> and <em>italic/emphasis text</em> and
	<mark>highlighted text</mark> and <del>deleted text</del> and <ins>inserted text</ins> and
	<small>small print</small> and <sup>superscript</sup> and <sub>subscript</sub>.
</p>

<!-- Abbreviation with tooltip -->
<p>The <abbr title="Golden Ratio">GR</abbr> is approximately 1.618.</p>

<!-- Horizontal rule -->
<hr />

Links inherit the accent color and have a custom underline offset. :focus-visible shows a keyboard-accessible focus ring without disrupting mouse users:

html
<a href="/guide/getting-started">Get Started</a>
<a href="https://github.com/dstN/remCSS" rel="noopener noreferrer">GitHub ↗</a>

Grid System

The grid component provides responsive layout containers. All layout is handled via CSS — no JavaScript, no class arithmetic.

Auto-Responsive Grid

Columns automatically adjust to available space. Minimum column width: 20rem (~200px):

html
<div class="grid-auto">
	<article class="card">Card 1</article>
	<article class="card">Card 2</article>
	<article class="card">Card 3</article>
	<article class="card">Card 4</article>
</div>

At narrow widths: single column. At medium: two columns. At wide: as many as fit. No breakpoints written.

A classic two-column layout: sidebar + main content. The sidebar has a natural width; the main takes all remaining space.

html
<div class="grid-sidebar">
	<aside class="sidebar">
		<nav>Navigation</nav>
	</aside>
	<main class="main-content">
		<h1>Page Title</h1>
		<p>Content area</p>
	</main>
</div>

Explicit Column Grid

html
<!-- 2 columns -->
<div class="grid cols-2 gap-2">
	<div>Column A</div>
	<div>Column B</div>
</div>

<!-- 3 columns -->
<div class="grid cols-3 gap-2">
	<div>One</div>
	<div>Two</div>
	<div>Three</div>
</div>

<!-- 4 columns -->
<div class="grid cols-4 gap-3">
	<div>Alpha</div>
	<div>Beta</div>
	<div>Gamma</div>
	<div>Delta</div>
</div>

Stack (Vertical Rhythm)

A vertical stack with consistent spacing between children:

html
<div class="stack stack-md">
	<h2>Section Title</h2>
	<p>First paragraph.</p>
	<p>Second paragraph.</p>
</div>

Stack gap sizes: stack-sm (step-1), stack-md (step-2), stack-lg (step-3), stack-xl (step-4).

Cluster (Inline Group)

Horizontal grouping of items that wraps gracefully:

html
<div class="cluster">
	<button class="button">Action 1</button>
	<button class="button button-outline">Action 2</button>
	<button class="button button-ghost">Cancel</button>
</div>