I had a weird thought while building a docs page last month.
I was writing this:
<button class="btn btn-primary">Get started</button>
And I thought — why does a button need to be told it's a button? It is a button. The browser knows it. The screen reader knows it. Why doesn't the CSS?
That thought turned into classless.css — a 47 KB stylesheet that makes plain, semantic HTML look great without adding a single class to your markup.
What does "classless" mean?
The idea is simple: instead of styling elements by class names, you style native HTML elements directly.
Normal CSS library:
<button class="btn btn-primary btn-lg">Submit</button>
<input class="input input-bordered" type="text">
<div class="card card-body shadow">Content</div>
Classless approach:
<button>Submit</button>
<input type="text">
<article>Content</article>
One <link> tag — and your HTML looks good. No class names to remember, no documentation to check for every element.
Who is this actually for?
Before I explain how it works, let me be honest about the use cases — because classless CSS is not for everything.
Perfect for:
- Markdown-rendered content (blogs, docs, READMEs rendered as HTML)
- Quick prototypes where you want something decent without thinking about classes
- Email templates and static pages with semantic HTML
- Dropping into an existing project to style a section without touching the markup
- Developers who just want to write HTML and move on
Not ideal for:
- Complex UI with many component variants
- Projects where you need full control over every detail
- Apps with existing CSS that would conflict
How it works
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/njx-ui/css/classless.min.css">
That's it. Now write plain HTML:
<main>
<h1>Hello World</h1>
<p>This paragraph is styled automatically.</p>
<button>Get started</button>
<button data-variant="accent">Secondary</button>
<input type="email" placeholder="your@email.com">
<textarea placeholder="Your message..."></textarea>
<table>
<thead><tr><th>Name</th><th>Role</th></tr></thead>
<tbody>
<tr><td>Alice</td><td>Designer</td></tr>
<tr><td>Bob</td><td>Developer</td></tr>
</tbody>
</table>
</main>
Every element — headings, paragraphs, buttons, inputs, tables, lists, code blocks, blockquotes — gets thoughtful styles automatically.
Try it live:
Variants without classes
The one concession to "no classes" is variants. Instead of class names, classless.css uses data-* attributes:
<!-- Button variants -->
<button>Default</button>
<button data-variant="accent">Accent</button>
<button data-variant="success">Success</button>
<button data-variant="danger">Danger</button>
<button data-variant="ghost">Ghost</button>
<!-- Input states -->
<input type="text" data-variant="success" value="Valid input">
<input type="text" data-variant="danger" placeholder="Error state">
<!-- Alert boxes -->
<div role="note">Neutral info</div>
<div role="note" data-variant="success">Operation complete</div>
<div role="note" data-variant="warning">Check this carefully</div>
<div role="note" data-variant="danger">Something went wrong</div>
data-variant keeps markup readable and semantic — you're describing what the element is, not what it should look like.
9 themes — same as the full library
classless.css shares the same theming system as the full njX UI library. Set data-theme on <html> and everything updates:
<html data-theme="dark"> <!-- default -->
<html data-theme="light">
<html data-theme="purple">
<html data-theme="cyan">
<!-- + red, blue, green, yellow, pink -->
Switch at runtime:
document.documentElement.setAttribute('data-theme', 'purple')
This works because both files use the same CSS custom properties from _base.css. The tokens are identical — only the scoping is different.
Scoping — it doesn't conflict with your existing CSS
Here's one thing I'm proud of: classless.css is fully scoped. Styles only activate inside elements that have no class:
:where(main:not([class]), article:not([class]), section:not([class]), form:not([class])) {
/* all classless styles live here */
}
This means you can use classless.css alongside style.min.css (the full library) or even Bootstrap — it will only apply where you have unclassed containers. No conflicts.
<!-- This gets classless styles -->
<main>
<h1>Styled automatically</h1>
<button>Looks great</button>
</main>
<!-- This is untouched -->
<div class="my-custom-section">
<button class="btn btn-primary">Full library button</button>
</div>
Loading states — no extra markup
One of my favorite parts: loading states are built in.
<!-- Busy container — overlay spinner appears automatically -->
<article aria-busy="true">
Loading content...
</article>
<!-- Loading button — spinner replaces text -->
<button aria-busy="true">Saving...</button>
<!-- Inline spinner -->
<span data-loading></span> Fetching data...
No JavaScript. No extra elements. Just semantic HTML attributes.
Details and dialog — CSS-only interactive
<!-- Accordion — native HTML, styled automatically -->
<details>
<summary>Click to expand</summary>
<p>Hidden content revealed with smooth animation.</p>
</details>
<!-- Dialog — open with JS, styled automatically -->
<dialog id="my-dialog">
<h2>Confirm action</h2>
<p>Are you sure?</p>
<button onclick="document.getElementById('my-dialog').close()">Close</button>
</dialog>
<button onclick="document.getElementById('my-dialog').showModal()">Open dialog</button>
Compared to PicoCSS
The closest thing to classless.css is PicoCSS, which I love and respect. The key differences:
| PicoCSS | njX classless.css | |
|---|---|---|
| Themes | 2 (light/dark) | 9 |
| Variant system | class-based | data-attribute |
| Loading states | ❌ | ✅ aria-busy + data-loading |
| Scoped (no conflicts) | ❌ | ✅ |
| Works with full library | ❌ | ✅ |
| Size | ~10 KB | 47 KB |
| Tooltip | ❌ | ✅ data-tooltip |
PicoCSS is smaller and more minimal. classless.css trades size for more features and better theming.
Try it
<!DOCTYPE html>
<html data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My page</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/njx-ui/css/classless.min.css">
</head>
<body>
<main>
<h1>Hello World</h1>
<p>Write semantic HTML. Get beautiful styles.</p>
<button>Default button</button>
<button data-variant="accent">Accent</button>
<input type="email" placeholder="your@email.com">
<details>
<summary>Learn more</summary>
<p>No classes needed. Just HTML.</p>
</details>
</main>
</body>
</html>
I had a weird thought while building a docs page last month.
I was writing this:
<button class="btn btn-primary">Get started</button>
And I thought — why does a button need to be told it's a button? It is a button. The browser knows it. The screen reader knows it. Why doesn't the CSS?
That thought turned into classless.css — a 47 KB stylesheet that makes plain, semantic HTML look great without adding a single class to your markup.
What does "classless" mean?
The idea is simple: instead of styling elements by class names, you style native HTML elements directly.
Normal CSS library:
<button class="btn btn-primary btn-lg">Submit</button>
<input class="input input-bordered" type="text">
<div class="card card-body shadow">Content</div>
Classless approach:
<button>Submit</button>
<input type="text">
<article>Content</article>
One <link> tag — and your HTML looks good. No class names to remember, no documentation to check for every element.
Who is this actually for?
Before I explain how it works, let me be honest about the use cases — because classless CSS is not for everything.
Perfect for:
- Markdown-rendered content (blogs, docs, READMEs rendered as HTML)
- Quick prototypes where you want something decent without thinking about classes
- Email templates and static pages with semantic HTML
- Dropping into an existing project to style a section without touching the markup
- Developers who just want to write HTML and move on
Not ideal for:
- Complex UI with many component variants
- Projects where you need full control over every detail
- Apps with existing CSS that would conflict
How it works
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/njx-ui/css/classless.min.css">
That's it. Now write plain HTML:
<main>
<h1>Hello World</h1>
<p>This paragraph is styled automatically.</p>
<button>Get started</button>
<button data-variant="accent">Secondary</button>
<input type="email" placeholder="your@email.com">
<textarea placeholder="Your message..."></textarea>
<table>
<thead><tr><th>Name</th><th>Role</th></tr></thead>
<tbody>
<tr><td>Alice</td><td>Designer</td></tr>
<tr><td>Bob</td><td>Developer</td></tr>
</tbody>
</table>
</main>
Every element — headings, paragraphs, buttons, inputs, tables, lists, code blocks, blockquotes — gets thoughtful styles automatically.
Try it live:
Variants without classes
The one concession to "no classes" is variants. Instead of class names, classless.css uses data-* attributes:
<!-- Button variants -->
<button>Default</button>
<button data-variant="accent">Accent</button>
<button data-variant="success">Success</button>
<button data-variant="danger">Danger</button>
<button data-variant="ghost">Ghost</button>
<!-- Input states -->
<input type="text" data-variant="success" value="Valid input">
<input type="text" data-variant="danger" placeholder="Error state">
<!-- Alert boxes -->
<div role="note">Neutral info</div>
<div role="note" data-variant="success">Operation complete</div>
<div role="note" data-variant="warning">Check this carefully</div>
<div role="note" data-variant="danger">Something went wrong</div>
data-variant keeps markup readable and semantic — you're describing what the element is, not what it should look like.
9 themes — same as the full library
classless.css shares the same theming system as the full njX UI library. Set data-theme on <html> and everything updates:
<html data-theme="dark"> <!-- default -->
<html data-theme="light">
<html data-theme="purple">
<html data-theme="cyan">
<!-- + red, blue, green, yellow, pink -->
Switch at runtime:
document.documentElement.setAttribute('data-theme', 'purple')
This works because both files use the same CSS custom properties from _base.css. The tokens are identical — only the scoping is different.
Scoping — it doesn't conflict with your existing CSS
Here's one thing I'm proud of: classless.css is fully scoped. Styles only activate inside elements that have no class:
:where(main:not([class]), article:not([class]), section:not([class]), form:not([class])) {
/* all classless styles live here */
}
This means you can use classless.css alongside style.min.css (the full library) or even Bootstrap — it will only apply where you have unclassed containers. No conflicts.
<!-- This gets classless styles -->
<main>
<h1>Styled automatically</h1>
<button>Looks great</button>
</main>
<!-- This is untouched -->
<div class="my-custom-section">
<button class="btn btn-primary">Full library button</button>
</div>
Loading states — no extra markup
One of my favorite parts: loading states are built in.
<!-- Busy container — overlay spinner appears automatically -->
<article aria-busy="true">
Loading content...
</article>
<!-- Loading button — spinner replaces text -->
<button aria-busy="true">Saving...</button>
<!-- Inline spinner -->
<span data-loading></span> Fetching data...
No JavaScript. No extra elements. Just semantic HTML attributes.
Details and dialog — CSS-only interactive
<!-- Accordion — native HTML, styled automatically -->
<details>
<summary>Click to expand</summary>
<p>Hidden content revealed with smooth animation.</p>
</details>
<!-- Dialog — open with JS, styled automatically -->
<dialog id="my-dialog">
<h2>Confirm action</h2>
<p>Are you sure?</p>
<button onclick="document.getElementById('my-dialog').close()">Close</button>
</dialog>
<button onclick="document.getElementById('my-dialog').showModal()">Open dialog</button>
Compared to PicoCSS
The closest thing to classless.css is PicoCSS, which I love and respect. The key differences:
| PicoCSS | njX classless.css | |
|---|---|---|
| Themes | 2 (light/dark) | 9 |
| Variant system | class-based | data-attribute |
| Loading states | ❌ | ✅ aria-busy + data-loading |
| Scoped (no conflicts) | ❌ | ✅ |
| Works with full library | ❌ | ✅ |
| Size | ~10 KB | 47 KB |
| Tooltip | ❌ | ✅ data-tooltip |
PicoCSS is smaller and more minimal. classless.css trades size for more features and better theming.
Try it
<!DOCTYPE html>
<html data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My page</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/njx-ui/css/classless.min.css">
</head>
<body>
<main>
<h1>Hello World</h1>
<p>Write semantic HTML. Get beautiful styles.</p>
<button>Default button</button>
<button data-variant="accent">Accent</button>
<input type="email" placeholder="your@email.com">
<details>
<summary>Learn more</summary>
<p>No classes needed. Just HTML.</p>
</details>
</main>
</body>
</html>
I had a weird thought while building a docs page last month.
I was writing this:
<button class="btn btn-primary">Get started</button>
And I thought — why does a button need to be told it's a button? It is a button. The browser knows it. The screen reader knows it. Why doesn't the CSS?
That thought turned into classless.css — a 47 KB stylesheet that makes plain, semantic HTML look great without adding a single class to your markup.
What does "classless" mean?
The idea is simple: instead of styling elements by class names, you style native HTML elements directly.
Normal CSS library:
<button class="btn btn-primary btn-lg">Submit</button>
<input class="input input-bordered" type="text">
<div class="card card-body shadow">Content</div>
Classless approach:
<button>Submit</button>
<input type="text">
<article>Content</article>
One <link> tag — and your HTML looks good. No class names to remember, no documentation to check for every element.
Who is this actually for?
Before I explain how it works, let me be honest about the use cases — because classless CSS is not for everything.
Perfect for:
- Markdown-rendered content (blogs, docs, READMEs rendered as HTML)
- Quick prototypes where you want something decent without thinking about classes
- Email templates and static pages with semantic HTML
- Dropping into an existing project to style a section without touching the markup
- Developers who just want to write HTML and move on
Not ideal for:
- Complex UI with many component variants
- Projects where you need full control over every detail
- Apps with existing CSS that would conflict
How it works
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/njx-ui/css/classless.min.css">
That's it. Now write plain HTML:
<main>
<h1>Hello World</h1>
<p>This paragraph is styled automatically.</p>
<button>Get started</button>
<button data-variant="accent">Secondary</button>
<input type="email" placeholder="your@email.com">
<textarea placeholder="Your message..."></textarea>
<table>
<thead><tr><th>Name</th><th>Role</th></tr></thead>
<tbody>
<tr><td>Alice</td><td>Designer</td></tr>
<tr><td>Bob</td><td>Developer</td></tr>
</tbody>
</table>
</main>
Every element — headings, paragraphs, buttons, inputs, tables, lists, code blocks, blockquotes — gets thoughtful styles automatically.
Try it live:
Variants without classes
The one concession to "no classes" is variants. Instead of class names, classless.css uses data-* attributes:
<!-- Button variants -->
<button>Default</button>
<button data-variant="accent">Accent</button>
<button data-variant="success">Success</button>
<button data-variant="danger">Danger</button>
<button data-variant="ghost">Ghost</button>
<!-- Input states -->
<input type="text" data-variant="success" value="Valid input">
<input type="text" data-variant="danger" placeholder="Error state">
<!-- Alert boxes -->
<div role="note">Neutral info</div>
<div role="note" data-variant="success">Operation complete</div>
<div role="note" data-variant="warning">Check this carefully</div>
<div role="note" data-variant="danger">Something went wrong</div>
data-variant keeps markup readable and semantic — you're describing what the element is, not what it should look like.
9 themes — same as the full library
classless.css shares the same theming system as the full njX UI library. Set data-theme on <html> and everything updates:
<html data-theme="dark"> <!-- default -->
<html data-theme="light">
<html data-theme="purple">
<html data-theme="cyan">
<!-- + red, blue, green, yellow, pink -->
Switch at runtime:
document.documentElement.setAttribute('data-theme', 'purple')
This works because both files use the same CSS custom properties from _base.css. The tokens are identical — only the scoping is different.
https://codepen.io/njbSaab/pen/vEyBvoN
Scoping — it doesn't conflict with your existing CSS
Here's one thing I'm proud of: classless.css is fully scoped. Styles only activate inside elements that have no class:
:where(main:not([class]), article:not([class]), section:not([class]), form:not([class])) {
/* all classless styles live here */
}
This means you can use classless.css alongside style.min.css (the full library) or even Bootstrap — it will only apply where you have unclassed containers. No conflicts.
<!-- This gets classless styles -->
<main>
<h1>Styled automatically</h1>
<button>Looks great</button>
</main>
<!-- This is untouched -->
<div class="my-custom-section">
<button class="btn btn-primary">Full library button</button>
</div>
Loading states — no extra markup
One of my favorite parts: loading states are built in.
<!-- Busy container — overlay spinner appears automatically -->
<article aria-busy="true">
Loading content...
</article>
<!-- Loading button — spinner replaces text -->
<button aria-busy="true">Saving...</button>
<!-- Inline spinner -->
<span data-loading></span> Fetching data...
No JavaScript. No extra elements. Just semantic HTML attributes.
Details and dialog — CSS-only interactive
<!-- Accordion — native HTML, styled automatically -->
<details>
<summary>Click to expand</summary>
<p>Hidden content revealed with smooth animation.</p>
</details>
<!-- Dialog — open with JS, styled automatically -->
<dialog id="my-dialog">
<h2>Confirm action</h2>
<p>Are you sure?</p>
<button onclick="document.getElementById('my-dialog').close()">Close</button>
</dialog>
<button onclick="document.getElementById('my-dialog').showModal()">Open dialog</button>
Compared to PicoCSS
The closest thing to classless.css is PicoCSS, which I love and respect. The key differences:
| PicoCSS | njX classless.css | |
|---|---|---|
| Themes | 2 (light/dark) | 9 |
| Variant system | class-based | data-attribute |
| Loading states | ❌ | ✅ aria-busy + data-loading |
| Scoped (no conflicts) | ❌ | ✅ |
| Works with full library | ❌ | ✅ |
| Size | ~10 KB | 47 KB |
| Tooltip | ❌ | ✅ data-tooltip |
PicoCSS is smaller and more minimal. classless.css trades size for more features and better theming.
Try it
<!DOCTYPE html>
<html data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My page</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/njx-ui/css/classless.min.css">
</head>
<body>
<main>
<h1>Hello World</h1>
<p>Write semantic HTML. Get beautiful styles.</p>
<button>Default button</button>
<button data-variant="accent">Accent</button>
<input type="email" placeholder="your@email.com">
<details>
<summary>Learn more</summary>
<p>No classes needed. Just HTML.</p>
</details>
</main>
</body>
</html>
Live docs: njxui.dev
Full classless reference: classless.md
npm: npm i njx-ui → css/classless.min.css
I've been using classless mode for quick internal tools and prototypes where I don't want to think about class names. It's genuinely refreshing to just write HTML and have it look decent.
What do you think — is classless CSS a useful pattern or just a fun experiment? Would love to hear how you handle styling for quick projects.
Live docs: njxui.dev
Full classless reference: classless.md
npm: npm i njx-ui → css/classless.min.css
I've been using classless mode for quick internal tools and prototypes where I don't want to think about class names. It's genuinely refreshing to just write HTML and have it look decent.
What do you think — is classless CSS a useful pattern or just a fun experiment? Would love to hear how you handle styling for quick projects.
Live docs: njxui.dev
Full classless reference: classless.md
npm: npm i njx-ui → css/classless.min.css
I've been using classless mode for quick internal tools and prototypes where I don't want to think about class names. It's genuinely refreshing to just write HTML and have it look decent.
What do you think — is classless CSS a useful pattern or just a fun experiment? Would love to hear how you handle styling for quick projects.




Top comments (0)