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 — its lines, its line numbers and its highlighted lines are all there on the first frame, so the colours are the only thing that arrives late, and nothing moves when they do.
A grammar is fetched the first time a block in that language is highlighted. Name the languages your documents use and they are fetched in the background once the first page is on screen, so moving to another page does not wait for one:
provideBlasdocHighlight({
themes: { github: { light: 'github-light', dark: 'github-dark' } },
defaultTheme: 'github',
langs: ['angular-ts', 'angular-html', 'bash', 'json'],
});Nothing in langs delays the first render, and a language left out is still
loaded the first time it is seen.
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.