Code blocks
Syntax highlighting, fence meta, line numbers and titles.
Fenced code is highlighted with Shiki and converted into
IR nodes — never an HTML string. It goes through the same renderer as the
rest of the document, which is why there is no innerHTML anywhere in Blasdoc.
The Angular grammars
Use angular-ts and angular-html rather than ts and html: they know about
decorators, control-flow blocks and the four binding channels, so the colours
say something true.
```angular-ts
@Component({ selector: 'app-button', template: '' })
export class AppButton {}
```import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
@Component({
selector: 'app-button',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<button type="button" (click)="clicked.emit()"><ng-content /></button>`,
})
export class AppButton {
readonly variant = input<'primary' | 'secondary'>('secondary');
readonly disabled = input(false);
readonly clicked = output<void>();
}@if (items().length) {
@for (item of items(); track item.id) {
<app-button [disabled]="locked()" (clicked)="buy(item)">
{{ item.title }}
</app-button>
}
} @else {
<p>Nothing to show.</p>
}Every fence option
Everything after the language is the meta string. Blasdoc reads the keys in the first table and hands the rest to the highlighter.
| Option | Effect |
|---|---|
title="app.component.ts" | names the block, and picks its icon |
icon="angular" | overrides the icon; icon="none" removes it |
terminal | marks the block a shell |
bare | no header at all; the copy control floats over the code |
collapse | folds the block, showing 10 lines |
collapse=6 | folds it, showing 6 |
tab="npm" | makes it a tab inside a code group |
theme="dracula" | a syntax theme for this block alone |
Passed through to the highlighter:
| Option | Effect |
|---|---|
{2,4-6} | highlights those lines |
showLineNumbers | numbers every line |
showLineNumbers{5} | numbers from 5 |
/pattern/ | highlights every occurrence of a word |
provideBlasdocHighlight({
themes: { github: { light: 'github-light', dark: 'github-dark' } },
defaultTheme: 'github',
defaultMeta: 'showLineNumbers',
});A titled block
```angular-ts title="accordion.component.ts"
export class Accordion {}
```import { ChangeDetectionStrategy, Component, contentChildren } from '@angular/core';
@Component({
selector: 'app-accordion',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<ng-content />`,
})
export class Accordion {}The icon is derived from what you already wrote — the title's extension, then
the language — so title="app.component.ts" is enough.
A folded block
Long files are worth showing without giving the whole page to them.
```angular-ts title="accordion.component.ts" collapse=6
…
```import {
type AfterContentInit,
ChangeDetectionStrategy,
Component,
computed,
contentChildren,
input,
ViewEncapsulation,
} from '@angular/core';
import type { ClassValue } from 'clsx';
@Component({
selector: 'app-accordion',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
template: `<div [class]="classes()"><ng-content /></div>`,
})
export class Accordion implements AfterContentInit {
readonly class = input<ClassValue>('');
readonly items = contentChildren(AccordionItem);
protected readonly classes = computed(() => this.class());
ngAfterContentInit(): void {
// …
}
}A bare block
No header, and the copy control appears over the code on hover — for a one-line snippet where a header would be more chrome than content.
```angular-html bare
<app-button variant="primary">Comprar</app-button>
```<app-button variant="primary">Comprar</app-button>A terminal
```bash terminal
npx @blasdoc/cli init
```npx @blasdoc/cli initCombined with a code group, that is the package-manager picker every installation section wants:
npm install @blasdoc/core @blasdoc/angularHighlighted lines
```angular-ts {3,5-6} title="app.config.ts"
…
```export const appConfig: ApplicationConfig = {
providers: [
provideBlasdoc(),
provideBlasdocHighlight({
themes: { github: { light: 'github-light', dark: 'github-dark' } },
defaultTheme: 'github',
}),
provideBlasdocTheme(),
],
};Loading
Shiki's grammars and themes are megabytes, so they are fetched on demand and only for the themes you registered. While that happens the block shows the same code, unhighlighted and identically shaped — no empty box, and nothing reflows when the colours arrive.
Replacing the block entirely
import { BLASDOC_CODE_BLOCK_COMPONENT } from '@blasdoc/angular';
providers: [{ provide: BLASDOC_CODE_BLOCK_COMPONENT, useValue: MyCodeBlock }];Every fence then renders as your component, with Blasdoc's state handed to it.
The behaviour is in @blasdoc/components — see
Headless directives.