Skip to content

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.

md
```angular-ts
@Component({ selector: 'app-button', template: '' })
export class AppButton {}
```
button.component.ts
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>();
}
button.component.html
@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.

OptionEffect
title="app.component.ts"names the block, and picks its icon
icon="angular"overrides the icon; icon="none" removes it
terminalmarks the block a shell
bareno header at all; the copy control floats over the code
collapsefolds the block, showing 10 lines
collapse=6folds 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:

OptionEffect
{2,4-6}highlights those lines
showLineNumbersnumbers every line
showLineNumbers{5}numbers from 5
/pattern/highlights every occurrence of a word
app.config.ts
provideBlasdocHighlight({
  themes: { github: { light: 'github-light', dark: 'github-dark' } },
  defaultTheme: 'github',
  defaultMeta: 'showLineNumbers',
});

A titled block

md
```angular-ts title="accordion.component.ts"
export class Accordion {}
```
accordion.component.ts
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.

md
```angular-ts title="accordion.component.ts" collapse=6
…
```
accordion.component.ts
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.

md
```angular-html bare
<app-button variant="primary">Comprar</app-button>
```
<app-button variant="primary">Comprar</app-button>

A terminal

md
```bash terminal
npx @blasdoc/cli init
```
npx @blasdoc/cli init

Combined with a code group, that is the package-manager picker every installation section wants:

npm install @blasdoc/core @blasdoc/angular

Highlighted lines

md
```angular-ts {3,5-6} title="app.config.ts"
…
```
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

ts
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.