Headless directives
Your markup, Blasdoc behaviour: tabs, copy, mode and keyboard.
@blasdoc/components holds behaviour with no opinion about pixels: selection,
the keyboard contract, ARIA wiring, the clipboard and highlight status. Apply
the directives to your own markup and you get all of it.
A code block that is entirely yours
import {
BlasdocCodeBlock,
BlasdocCodeBlockState,
BlasdocCopy,
BlasdocTab,
BlasdocTabList,
BlasdocTabPanel,
} from '@blasdoc/components';
@Component({
selector: 'my-snippet',
imports: [BlasdocTabList, BlasdocTab, BlasdocTabPanel, BlasdocCopy],
hostDirectives: [BlasdocCodeBlock],
template: `
<figure class="my-snippet">
<figcaption>
<div blasdocTabList aria-label="Package manager">
@for (tab of state.tabs(); track tab.id) {
<button [blasdocTab]="tab.id">{{ tab.label }}</button>
}
</div>
<button blasdocCopy blasdocCopyLabel="Copy this snippet">Copy</button>
</figcaption>
<div blasdocTabPanel>
<div #surface></div>
</div>
</figure>
`,
})
export class MySnippet {
protected readonly state = new BlasdocCodeBlockState({ tabs: [...] });
private readonly block = inject(BlasdocCodeBlock, { self: true });
constructor() {
this.block.use(this.state);
}
}Nothing above comes from the default theme. What comes from Blasdoc is the behaviour.
The directives
| Directive | What it gives you |
|---|---|
blasdocCodeBlock | the host; holds the state every other directive reads |
blasdocTabList | role="tablist", arrow / Home / End keys, roving focus |
blasdocTab | role="tab", aria-selected, aria-controls, tabindex |
blasdocTabPanel | role="tabpanel", labelled by the active tab |
blasdocCopy | copies the active tab, sets data-copied, accessible name |
blasdocMode | toggles between preview and code |
A .blasdoc-tab-active class marks the selected tab so you can style it; the
look is entirely yours.
The state
interface BlasdocCodeBlockState {
readonly tabs: Signal<readonly BlasdocCodeTab[]>;
readonly activeTab: Signal<BlasdocCodeTab | null>;
readonly mode: Signal<'preview' | 'code'>;
readonly status: Signal<'idle' | 'loading' | 'ready' | 'failed'>;
readonly copied: Signal<boolean>;
selectTab(id: string): void;
setMode(mode: 'preview' | 'code'): void;
handleKey(key: string): boolean;
copy(): Promise<void>;
}