HTML components.
Zero runtime.
What if you could have components that were just HTML files without any special syntax? Now you can. Bascik is a build tool for HTML components with automatically scoped CSS and JS. Zero runtime. The code that ships is the code you wrote.
npm create bascik@latest- 0 kB framework runtime shipped
- 0 required runtime dependencies
- Use what you know: vanilla HTML, CSS, JS
- No imports or exports: components just work
How it works
No imports. No exports. Just use the tag.
Create a file named after your component and add your HTML, CSS, and JavaScript. Then use that tag in any page or component. Bascik finds it automatically, with no import, export, or registration step.
<!-- src/pages/index.html -->
<!DOCTYPE html>
<html lang="en">
<body>
<site-nav />
<h1>Welcome</h1>
</body>
</html> <!-- src/components/site-nav.html -->
<style>
.nav {
display: flex;
gap: 24px;
padding: 16px 28px;
border-bottom: 3px solid #d3ff8d;
}
.link { color: #d3ff8d; }
.link:hover { text-decoration: underline; }
</style>
<nav class="nav">
<a class="link" href="/">Home</a>
<a class="link" href="/why-bascik">Why Bascik</a>
<a class="link" href="/getting-started">Docs</a>
</nav> <!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.bascik__site-nav__nav {
display: flex;
gap: 24px;
padding: 16px 28px;
border-bottom: 3px solid #d3ff8d;
}
.bascik__site-nav__link { color: #d3ff8d; }
.bascik__site-nav__link:hover { text-decoration: underline; }
</style>
</head>
<body>
<nav class="bascik__site-nav__nav">
<a class="bascik__site-nav__link" href="/">Home</a>
<a class="bascik__site-nav__link" href="/components">Components</a>
<a class="bascik__site-nav__link" href="/slots">Slots</a>
</nav>
<h1>Welcome</h1>
</body>
</html> Live Example
Welcome
Scoping
One component. Multiple isolated instances.
Bascik scopes CSS class names and DOM selectors at build time. Each instance gets its own namespace with no runtime JS, no Shadow DOM, and no virtual DOM.
<!-- src/pages/index.html -->
<!DOCTYPE html>
<html lang="en">
<body>
<demo-counter data-bascik-prop-label="Instance A" />
<demo-counter data-bascik-prop-label="Instance B" />
</body>
</html> <!-- src/components/demo-counter/demo-counter.html -->
<div class="ctr">
<span class="ctr-label" data-bascik-prop-label>
Counter
</span>
<span class="ctr-count" id="count">0</span>
<div class="ctr-btns">
<button class="ctr-dec" id="dec">−</button>
<button class="ctr-inc" id="inc">+</button>
</div>
</div>
<script src="demo-counter.ts"></script> /* src/components/demo-counter/demo-counter.css */
.ctr {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.ctr-count {
font-size: 2.4rem;
font-weight: 700;
color: #d3ff8d;
}
.ctr-dec, .ctr-inc {
width: 40px;
height: 40px;
border-radius: 6px;
cursor: pointer;
} // src/components/demo-counter/demo-counter.ts
const count = document.getElementById('count') as HTMLElement;
const dec = document.getElementById('dec') as HTMLButtonElement;
const inc = document.getElementById('inc') as HTMLButtonElement;
let n: number = 0;
dec.addEventListener('click', () => {
n--;
count.textContent = String(n);
});
inc.addEventListener('click', () => {
n++;
count.textContent = String(n);
}); <!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.bascik__demo-counter__ctr {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.bascik__demo-counter__ctr-count {
font-size: 2.4rem;
font-weight: 700;
color: #d3ff8d;
}
.bascik__demo-counter__ctr-dec,
.bascik__demo-counter__ctr-inc {
width: 40px;
height: 40px;
border-radius: 6px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="bascik__demo-counter__ctr">
<span class="bascik__demo-counter__ctr-label">Instance A</span>
<span class="bascik__demo-counter__ctr-count"
id="bascik__demo-counter__a1b2__count">0</span>
<div class="bascik__demo-counter__ctr-btns">
<button class="bascik__demo-counter__ctr-dec"
id="bascik__demo-counter__a1b2__dec">−</button>
<button class="bascik__demo-counter__ctr-inc"
id="bascik__demo-counter__a1b2__inc">+</button>
</div>
</div>
<script>
(function() {
const count = document.getElementById("bascik__demo-counter__a1b2__count");
const dec = document.getElementById("bascik__demo-counter__a1b2__dec");
const inc = document.getElementById("bascik__demo-counter__a1b2__inc");
let n = 0;
dec.addEventListener("click", () => { n--; count.textContent = String(n); });
inc.addEventListener("click", () => { n++; count.textContent = String(n); });
})();
</script>
</body>
</html> Composition
Your components work just like native HTML components.
No synthetic lifecycle hooks, no virtual DOM diffing, and no proprietary event wrappers. Pass slots, attributes, and regular HTML tags just like you would with native browser elements.
<!-- src/pages/index.html -->
<dialog-box>
<span data-bascik-slot="header">Account Settings</span>
<p>Your components accept slots, attributes, and regular HTML tags.</p>
<button type="button">Save Changes</button>
</dialog-box> <!-- src/components/dialog-box.html -->
<style>
.dialog {
border: 1px solid #3a3d40;
border-radius: 8px;
padding: 20px;
background: #1e2022;
}
.header {
border-bottom: 1px solid #2e3032;
padding-bottom: 10px;
margin-bottom: 14px;
font-weight: 700;
}
</style>
<div class="dialog">
<div class="header">
<div data-bascik-slot="header">
<span>Notice</span>
</div>
</div>
<div class="body">
<div data-bascik-slot>
<p>Default content if no slot content was provided.</p>
</div>
</div>
</div> <!-- dist/index.html -->
<style>
.bascik__dialog-box__dialog {
border: 1px solid #3a3d40;
border-radius: 8px;
padding: 20px;
background: #1e2022;
}
.bascik__dialog-box__header {
border-bottom: 1px solid #2e3032;
padding-bottom: 10px;
margin-bottom: 14px;
font-weight: 700;
}
</style>
<div class="bascik__dialog-box__dialog">
<div class="bascik__dialog-box__header">
<span>Account Settings</span>
</div>
<div class="bascik__dialog-box__body">
<p>Your components accept slots, attributes, and regular HTML tags.</p>
<button type="button">Save Changes</button>
</div>
</div> Native Feel
Standard DOM & Slots
Default slots, named slots, and fallback content flatten at build time. The output is clean, standard markup that any browser understands.
Read the Slots guide →Full-Stack Scripts
Build time, request time, or streamed live. Zero client JS.
Standard Node.js right in your HTML. Run scripts at build time for SSG, per request on the server, or stream HTML chunks progressively over an open HTTP connection without a client framework runtime.
Build Scripts
Run Node.js once at build time. Fetch data, query databases, or render Markdown directly into your static pages before shipping.
Build Scripts guide →Server Scripts
Run on each request. Read cookies, check auth headers, and buffer responses. Aborts with a 500 before headers commit if an error happens.
Server Scripts guide →Stream Scripts
Flush the static shell and placeholder UI immediately, then stream resolved HTML chunks over the open HTTP connection as backend work finishes.
Stream Scripts guide →<!-- 1. Skeletons and footer flush in the very first byte. Zero wait. -->
<div class="stream-layout">
<div class="skeleton" role="status">Loading live metrics...</div>
<!-- 2. Async backend finishes, flushes straight into the open connection -->
<div class="slot">
<script data-bascik-stream>
import { escape } from '@/lib/server.ts';
export default async function (request, context, { signal }) {
const stats = await fetchDatabaseStats({ signal });
return `<article class="card"><h3>${escape(stats.cluster)}</h3><p>99.99% Uptime</p></article>`;
}
</script>
</div>
</div> /* 3. Pure CSS: :has() swaps the skeleton out the moment HTML arrives */
.stream-layout:has(.card) .skeleton {
display: none;
}
/* 4. CSS flex order: footer sent in chunk 1, but sits visually at the bottom */
footer {
order: 99;
} Progressive streaming without a client framework
Browsers have parsed chunked HTML since the 90s. When you stream HTML with Bascik, the browser displays the page as it arrives. No React hydration, no virtual DOM diffing, and no client-side fetch polling.
- Immediate first paint: Static shell and skeleton placeholders flush right away.
- Zero layout shift: CSS
:has()swaps out the skeleton in place when the HTML chunk arrives. - Early-paint footer: Send the footer in chunk 1, position it visually with CSS
order.

Live Reload
Save a file. The page is already updated.
Edit a page or a component and the dev server rebuilds only what changed, then refreshes the browser. Change one component and every page that uses it changes with it.

npm create bascik, edited in VS Code and served by the Bascik dev server. Nothing here is mocked.Build Performance
Page builds in milliseconds. Zero runtime overhead.
The dev server and the production build run the exact same pipeline, and it is tuned for performance. Component scoping runs once, then every instance reuses the result. Build script output is cached, so only scripts whose inputs changed run again. The dev server serves files from memory. Unchanged assets are symlinked instead of copied. Watch the terminal: the server is up almost instantly, and edits are back in the browser before you have switched windows. See how the dev server works.

npm create bascik project, recorded for real on a laptop: the server is up almost immediately, its address stays pinned to the top row, and saving one file rebuilds only that page.Developer Experience
Built for developer joy.
Component modularity and automatic scoping without the framework friction, complex setups, or runtime overhead.
Works instantly
No bundler setups, complex configs, or transpiler chains. Run bascik and start building instantly with standard Node.js.
Lean in production
Zero production dependencies at build and server runtime. A single optional dependency (chokidar) powers local dev file watching.
Standard HTML & CSS
No JSX, custom template syntax, or framework hooks. Write pure HTML and CSS with standard DOM APIs. Everything you know just works.
Automatic isolation
Component styles and DOM queries are scoped automatically at build time. Enjoy total component isolation with zero added JavaScript.
Docs
Explore the docs.
Each feature has a dedicated page with examples and a complete reference.
Core
Components
Build UI with reusable HTML components. No imports or registration needed. Just write the custom tag.
CSS
Scoped Styles
Class names, element selectors, @media, and @keyframes are all automatically scoped per component.
JS
Scoped JavaScript
DOM queries in component scripts are rewritten to match scoped IDs and class names at build time.
Slots
Slots
Pass inner HTML into components. Named slots target specific zones with fallback content supported.
Build
Build Scripts
Run arbitrary Node.js at build time with data-bascik-build script blocks. Fetch data, render Markdown, generate anything.
Config
Configuration
Override defaults in bascik.config.ts. Toggle scoping, identifier minification, and code minification per project.
Speed
No Framework Runtime
No framework bundle or hydration payload. Your content, assets, and markup determine the final Lighthouse scores.
Switching
Switch to Bascik
Moving from React, Next.js, Eleventy, or Astro. Concept mapping and worked examples.
Ready to build?
Up and running in under a minute.
One command scaffolds your project and starts the dev server. No config required to get going.