One file. One tag.
Plain HTML, ready to reuse.
Bascik replaces the custom tag at build time and ships the finished markup.
Features
Components are the core building block in Bascik. Each component is a .html file in src/components/. The file name becomes the tag name used in your pages and in other components.
The simplest component is just markup. No CSS, no JavaScript required.
Styles omitted for clarity. This demo component includes a companion .css file that styles the card. The CSS is not shown in the source view so the demo stays focused on the HTML structure. Note that output code examples across the documentation show unminified output (HTML, CSS, JS, and identifier names) for readability.
Components can appear in other components too. If site-layout.html uses <page-footer></page-footer>, every page that uses <site-layout> gets the footer automatically.
One file. One tag.
Bascik replaces the custom tag at build time and ships the finished markup.
<!DOCTYPE html>
<html lang="en">
<body>
<hello-card />
</body>
</html> <article class="hello-card">
<p class="hello-card-kicker">One file. One tag.</p>
<h3 class="hello-card-title">Plain HTML, ready to reuse.</h3>
<p class="hello-card-body">Bascik replaces the custom tag at build time and ships the finished markup.</p>
</article> <!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.bascik__hello-card__hello-card {
width: min(100%, 32rem);
padding: 24px;
background: var(--elevated);
border: 1px solid var(--border);
border-top: 3px solid var(--accent);
border-radius: 6px;
}
</style>
</head>
<body>
<article class="bascik__hello-card__hello-card">
<p class="bascik__hello-card__hello-card-kicker">One file. One tag.</p>
<h3 class="bascik__hello-card__hello-card-title">Plain HTML, ready to reuse.</h3>
<p class="hello-card-body">Bascik replaces the custom tag at build time and ships the finished markup.</p>
</article>
</body>
</html> Add a <style> block to style the component inline. Bascik scopes the CSS to the component at build time so selectors from one component never affect another.
More on the next page. Scoped Styles covers exactly how class names and selectors are namespaced and what CSS patterns are supported.
<comp-badge /> <style>
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
background: #d1fae5;
color: #065f46;
border-radius: 999px;
font-size: 0.8rem;
font-weight: 600;
}
.badge-dot {
width: 6px;
height: 6px;
background: #10b981;
border-radius: 50%;
flex-shrink: 0;
}
</style>
<span class="badge">
<span class="badge-dot"></span>
All systems operational
</span> <span class="bascik__comp-badge__badge">
<span class="bascik__comp-badge__badge-dot"></span>
All systems operational
</span> .bascik__comp-badge__badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
background: #d1fae5;
color: #065f46;
border-radius: 999px;
font-size: 0.8rem;
font-weight: 600;
}
.bascik__comp-badge__badge-dot {
width: 6px;
height: 6px;
background: #10b981;
border-radius: 50%;
flex-shrink: 0;
} Add a <script> block for interactive behavior. Use id on any element you need to target from JS and reach it with getElementById.
Styles omitted for clarity. This demo component includes a companion .css file that styles the card. The CSS is not shown in the source view so the demo stays focused on the JavaScript. Note that output code examples across the documentation show unminified output (HTML, CSS, JS, and identifier names) for readability.
See also. Scoped JavaScript explains how Bascik rewrites selectors so multiple instances of the same component on the same page stay fully independent.
Bascik assembles components at build time and ships vanilla HTML files with no framework runtime.
No JavaScript is added to the page. Every script in the output was written by you.
<comp-toggle /> <div class="toggle-wrap">
<p>Bascik assembles components at build time and ships vanilla HTML files with no framework runtime.</p>
<div id="detail" hidden>
<p>No JavaScript is added to the page. Every script in the output was written by you.</p>
</div>
<button id="btn" type="button">Read more</button>
</div> const btn = document.getElementById('btn');
const detail = document.getElementById('detail');
btn.addEventListener('click', () => {
detail.hidden = !detail.hidden;
btn.textContent = detail.hidden ? 'Read more' : 'Show less';
}); <div class="bascik__comp-toggle__toggle-wrap">
<p class="bascik__comp-toggle__el__p">Bascik assembles components at build time and ships vanilla HTML files with no framework runtime.</p>
<div id="bascik__comp-toggle__a1b__detail" hidden>
<p class="bascik__comp-toggle__el__p">No JavaScript is added to the page. Every script in the output was written by you.</p>
</div>
<button class="bascik__comp-toggle__el__button" id="bascik__comp-toggle__a1b__btn" type="button">Read more</button>
</div>
<script>
(function(){
const btn = document.getElementById('bascik__comp-toggle__a1b__btn');
const detail = document.getElementById('bascik__comp-toggle__a1b__detail');
btn.addEventListener('click', () => {
detail.hidden = !detail.hidden;
btn.textContent = detail.hidden ? 'Read more' : 'Show less';
});
})();
</script> .bascik__comp-toggle__toggle-wrap {
padding: 24px;
background: var(--elevated);
border: 1px solid var(--border);
border-radius: var(--r);
max-width: 420px;
}
.bascik__comp-toggle__toggle-wrap .bascik__comp-toggle__el__p {
color: var(--text-muted);
margin: 0 0 16px;
}
.bascik__comp-toggle__toggle-wrap .bascik__comp-toggle__el__button {
display: inline-block;
padding: 8px 18px;
background: var(--accent);
color: #18191b;
border: none;
border-radius: var(--r-sm);
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
} All three can live in a single file. Follow the component convention order: place <style> blocks above markup, then markup, then <script> blocks below markup.
Scheduled maintenance Sunday, 2am–4am UTC.
<comp-alert /> <style>
.alert {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 16px;
border: 1px solid #f59e0b;
border-radius: 8px;
background: #fffbeb;
}
.alert-body {
flex: 1;
margin: 0;
font-size: 0.9rem;
color: #92400e;
}
.alert-close {
background: none;
border: none;
cursor: pointer;
color: #b45309;
font-size: 1.2rem;
line-height: 1;
padding: 0;
}
</style>
<div class="alert" id="alert">
<p class="alert-body">Scheduled maintenance Sunday, 2am–4am UTC.</p>
<button id="close" class="alert-close" aria-label="Dismiss">×</button>
</div>
<script>
document.getElementById('close').addEventListener('click', () => {
document.getElementById('alert').hidden = true;
});
</script> <!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.bascik__comp-alert__alert {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 16px;
border: 1px solid #f59e0b;
border-radius: 8px;
background: #fffbeb;
}
.bascik__comp-alert__alert-body {
flex: 1;
margin: 0;
font-size: 0.9rem;
color: #92400e;
}
.bascik__comp-alert__alert-close {
background: none;
border: none;
cursor: pointer;
color: #b45309;
font-size: 1.2rem;
line-height: 1;
padding: 0;
}
</style>
</head>
<body>
<div class="bascik__comp-alert__alert" id="bascik__comp-alert__a1b__alert">
<p class="bascik__comp-alert__alert-body">Scheduled maintenance Sunday, 2am–4am UTC.</p>
<button id="bascik__comp-alert__a1b__close" class="bascik__comp-alert__alert-close" aria-label="Dismiss">×</button>
</div>
<script>
(function(){
document.getElementById('bascik__comp-alert__a1b__close').addEventListener('click', () => {
document.getElementById('bascik__comp-alert__a1b__alert').hidden = true;
});
})();
</script>
</body>
</html> .bascik__comp-alert__alert {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 16px;
border: 1px solid #f59e0b;
border-radius: 8px;
background: #fffbeb;
}
.bascik__comp-alert__alert-body {
flex: 1;
margin: 0;
font-size: 0.9rem;
color: #92400e;
}
.bascik__comp-alert__alert-close {
background: none;
border: none;
cursor: pointer;
color: #b45309;
font-size: 1.2rem;
line-height: 1;
padding: 0;
} Choosing between inline <style> or <script> blocks and companion .css or .ts/.js/.mjs files is a matter of personal preference.
For inline component blocks, keep <style> above the component markup and <script> below the markup. This convention is now validated by bascik --check as an advisory warning (component-structure-order).
Create a .css file or companion script files alongside the .html file if you prefer to keep your styles or JavaScript separate. Companion .css files in the same component directory are merged automatically. Companion script files explicitly referenced via <script src="counter.ts"></script> are resolved, inlined, and scoped at build time. A .ts or .mts companion has its erasable TypeScript stripped automatically before scoping, with no configuration; see TypeScript in Component Scripts.
Directory Isolation Rule: Path resolution for companion files is strictly scoped to the component's directory or base filename. A component inside src/components/demo-counter/ can only reference script or style files located inside its own folder. It cannot access files across other component directories.
The directive family also includes data-bascik-attr-{attribute}="{propName}" for sending a prop value to an attribute on an element inside the component, and data-bascik-preserve for keeping selected id, name, or class values literal. Use content props for text, attribute props for values such as href, src, and aria-label, slots for consumer-authored markup, and Preserve Scoping only when an external system must bypass normal scoping.
Bascik resolves tags and ships vanilla HTML.
<feature-card
data-bascik-prop-title="Build-time Components"
data-bascik-prop-desc="Bascik resolves tags and ships vanilla HTML.">
</feature-card> <div class="fcard">
<h3 data-bascik-prop-title></h3>
<p data-bascik-prop-desc></p>
</div> .fcard {
padding: 24px;
background: #242628;
border: 1px solid #3a3d40;
border-radius: 10px;
h3 { color: #f0f1f2; }
p { font-size: 0.875rem; color: #8d929e; }
&:hover {
border-color: rgba(211,255,141,0.35);
box-shadow: 0 0 0 1px rgba(211,255,141,0.12);
}
} <div class="bascik__feature-card__fcard">
<h3>Build-time Components</h3>
<p>Bascik resolves tags and ships vanilla HTML.</p>
</div> .bascik__feature-card__fcard {
padding: 24px;
background: #242628;
border: 1px solid #3a3d40;
border-radius: 10px;
}
.bascik__feature-card__fcard .bascik__feature-card__el__h3 {
color: #f0f1f2;
}
.bascik__feature-card__fcard .bascik__feature-card__el__p {
font-size: 0.875rem;
color: #8d929e;
}
.bascik__feature-card__fcard:hover {
border-color: rgba(211,255,141,0.35);
box-shadow: 0 0 0 1px rgba(211,255,141,0.12);
} Organize components into subfolders whenever it helps keep your project tidy. You can group by feature, section, or per-component folders containing companion files:
src/components/
marketing/
promo-card.html
promo-card.css
admin/
user-row.html
alert-box/
alert-box.html
alert-box.css
alert-box.ts Bascik derives the tag name from the filename only, not from the directory path. Subfolders do not create separate namespaces, so marketing/promo-card.html registers <promo-card>, admin/user-row.html registers <user-row>, and alert-box/alert-box.html registers <alert-box>.
If two files anywhere in src/components/ resolve to the same tag name (e.g. marketing/card.html and admin/card.html), Bascik raises a build error naming both conflicting paths.
Flexible organization. Organize src/components/ flat, in feature folders, or in per-component directories. Choose whichever layout fits your workflow and keep filenames unique across the tree.
No restart needed. The dev server watches src/components/ for new and changed files. Drop in a new .html or .css file and all affected pages re-transpile and reload automatically.
Bascik gives you complete freedom to structure your components however you prefer. You can mix and match different layouts across your project depending on the size and complexity of each component:
.html file inside src/components/. No CSS/JS files needed..html file inside src/components/ containing both your markup and <style>/<script> blocks..html file and a .css file side-by-side in src/components/ (e.g. src/components/my-card.html and src/components/my-card.css).src/components/ containing the matching files (e.g. src/components/my-card/my-card.html and src/components/my-card/my-card.css).You can choose any of these arrangements at any time. There is no functionality or performance difference between them. At build time, Bascik treats them identically, extracting and scoping your styles and bundling them into the page's final compiled output.
Under the WHATWG HTML standard (§4.13.1.2), custom elements must contain at least one hyphen (such as <site-nav>, <hello-card>, or <my-button>).
Naming your components with hyphens ensures they never collide with native HTML tags or future web standards. Bascik's CLI compiler and the official Bascik VS Code Extension validate component names at build time and in your editor:
header.html or dialog.html), Bascik issues a build-time warning indicating that it may collide with standard HTML elements.card.html), Bascik compiles it for backward compatibility, but issues a warning recommending a hyphenated name like my-card.html or site-card.html.Always use lowercase hyphenated filenames in src/components/ (e.g. src/components/feature-card.html for <feature-card>).
Component names match complete tag names only. A component named card claims <card>, but it never claims a longer tag such as <card-header>.
When utilizing your components inside pages or other components, choose self-closing void syntax for any component that does not take slot children:
<!-- Preferred void (self-closing) syntax for components without slots -->
<site-nav />
<site-head />
<site-footer />
<!-- Paired tags (only required when passing inner slot content) -->
<hello-card>
<p>Slot content goes here.</p>
</hello-card> If a component does not use a <slot> to accept inner children, always prefer self-closing/void syntax (<site-nav />, <site-footer />) to keep page markup clean, concise, and readable. A space before /> is optional, so <site-nav /> and <site-nav/> compile identically, including when nested inside another instance of the same component. Paired and self-closing forms compile to the exact same output.
Unlike traditional JavaScript frameworks (such as React or Vue 2) that historically required a single root wrapper element or explicit fragment components, Bascik component templates naturally support multiple top-level HTML elements in a single .html file.
<h2>Section Heading</h2>
<p class="intro">Introductory paragraph text.</p>
<div class="card">Card content</div> When transpiled, all root-level elements are inserted directly into the page markup in order. No unnecessary wrapper <div> or <Fragment> tags are added to your rendered HTML. Bascik's scoping engine automatically handles CSS rules, class names, IDs, element selectors, and scripts across every element in the component template.
Attribute Inheritance: If non-data-bascik-* attributes are passed on a usage tag (such as class="extra" or aria-label="Section"), Bascik merges them onto the first root HTML element in the component template.
When writing component files containing both styles and scripts alongside HTML, always place <style> tags above the HTML markup as a style guide, and always place <script> tags below the HTML markup.
You can include multiple <style> blocks in a single component file. For example, you can organize styles into separate blocks for general layout, media queries, or themes.
<style>
.card { padding: 16px; background: #1a1b1e; }
</style>
<div class="card">
<h3>Card Title</h3>
</div>
<style media="(min-width: 768px)">
.card { padding: 24px; }
</style> At build time, Bascik extracts every <style> block from the component file, combines them with any companion .css file, applies class and selector scoping, and injects the resulting CSS into the document <head>.
Readability & Maintainability: While Bascik supports multiple <style> tags in a single component file, using multiple <style> tags (or mixing an inline <style> tag with a companion .css file) is not recommended for readability and maintainability. Choose a single stylesheet pattern per component.
Component templates can also contain multiple <script> tags. Bascik handles each script according to its type and attributes:
data-bascik-server or data-bascik-build) are each wrapped in an isolated IIFE (function() { ... })();. You can include multiple client scripts in a component template, and each receives its own scope so local variables do not bleed into other blocks.<script data-bascik-build>): Executed during build or dev time in Node.js, replacing the tag with its stdout. Multiple build scripts execute concurrently.<script data-bascik-server>): Executed on the server at request time in Node.js. They are not wrapped in browser IIFEs.type="application/ld+json"): Preserved intact without IIFE wrapping or JavaScript minification.Clean Code Recommendation: Using separate <script> tags for distinct, unrelated concerns within a component (for example, a modal controller vs. an analytics handler) is recommended for code readability and maintainability. Because Bascik wraps each client script in its own IIFE, local variables stay safely isolated without polluting a single monolithic script block. Avoid splitting closely related code for no reason, but use separate <script> tags whenever a component handles multiple independent interactive features.
Understanding the distinction between page shells (src/pages/*.html) and component templates (src/components/*.html) is key to structuring accessible layouts:
src/components/*.html): Component markup is scoped at build time. Class names are namespaced, and id attributes are hashed per instance (e.g. id="bascik__comp__a1b2__btn").src/pages/*.html): Page markup is unscoped vanilla HTML. IDs, landmarks, and attributes written directly in page files remain literal strings (id="main-content").When structuring or migrating a site with Bascik, identify repeating HTML markup across page shells (especially shared <head> tags such as meta charset, viewport, favicons, web fonts, Open Graph tags, global CSS links, and site headers or footers) and extract them into reusable components.
Component tags can be placed inside <head> element blocks in your page shells:
<!-- src/components/site-head.html -->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="stylesheet" href="/styles.css" /> Page shells can then include <site-head /> inside their <head>:
<!-- src/pages/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<site-head />
<title>Home - My Site</title>
</head>
<body>
<site-nav />
<main id="main-content">
<h1>Welcome</h1>
</main>
<site-footer />
</body>
</html> At build time, Bascik expands <site-head> inside <head> and merges any component styles into the document <head> naturally.
Under WCAG 2.4.1 (Bypass Blocks), a "Skip to main content" link must be the first focusable element in the DOM so keyboard users pressing Tab can skip past header navigation links directly to the page's primary content.
Placing the skip link at the top of a <site-nav> component guarantees it is rendered at the top of every page, while targeting an unscoped id="main-content" on the page shell's <main> landmark:
<!-- src/components/site-nav.html (First focusable element in DOM) -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav class="dnav" aria-label="Main">
<a href="/" class="dnav-logo">Logo</a>
<!-- Navigation links... -->
</nav>
<!-- src/pages/about.html (Page Shell: unscoped HTML landmark) -->
<!DOCTYPE html>
<html lang="en">
<body>
<site-nav />
<!-- Target landmark: ID remains unscoped and literal for #main-content anchor -->
<main id="main-content" class="docs-content">
<h1>About Us</h1>
<p>Page content...</p>
</main>
</body>
</html> Because id="main-content" is written on the page shell (<main id="main-content">), the id is not hashed, ensuring the component's <a href="#main-content"> skip link always resolves cleanly to the main landmark.
Components can be shared across projects or distributed as npm packages:
bascik add <package> to copy components from npm packages directly into your project's src/components/ directory. See Sharing Components.bascik.components directory contract to allow other developers to add your components. See Publishing Components.