shikidown

Quick start

From an empty Angular project to a rendered document with a live Angular component inside it.

Register the provider

provideMarkdown() returns EnvironmentProviders, so it goes in the providers array of your application config.

app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideMarkdown } from 'shikidown';

export const appConfig: ApplicationConfig = {
  providers: [
    provideMarkdown({
      theme: { dark: 'github-dark', light: 'catppuccin-latte' },
    }),
  ],
};

Every option has a default, so an empty call works too. See Configuration for the full list.

Render a document

Import MarkdownComponent and give it some Markdown.

docs-page.ts
import { Component } from '@angular/core';
import { MarkdownComponent } from 'shikidown';

@Component({
  selector: 'app-docs-page',
  imports: [MarkdownComponent],
  template: `<shikidown [content]="md" class="prose dark:prose-invert max-w-none" />`,
})
export class DocsPage {
  readonly md = `# Hello

This is **shikidown**.

\`\`\`typescript
const answer: number = 42;
\`\`\`
`;
}

The class lands on the host element. shikidown ships no styles of its own, so this is where you attach your typography — see Styling and dark mode.

Put a component in the Markdown

Register a component against a selector, then write that selector in the document.

counter.ts
import { Component, input, numberAttribute, signal } from '@angular/core';

@Component({
  selector: 'my-counter',
  template: `<button (click)="count.set(count() + 1)">{{ label() }}: {{ count() }}</button>`,
})
export class CounterComponent {
  readonly initialCount = input(0, { transform: numberAttribute });
  readonly label = input('Clicks');
  readonly count = signal(0);

  constructor() {
    this.count.set(this.initialCount());
  }
}
app.config.ts
provideMarkdown({
  components: { 'my-counter': CounterComponent },
});
the document
# My page

<my-counter initial-count="5" label="Votes"></my-counter>

The rendered counter is a real Angular component: it has its own state, its own change detection, and its inputs are fed from the HTML attributes. Note the kebab-case attribute mapping to the camelCase input — the browser does that for you, and it is covered in Embedding Angular components.

Turn on incremental rendering

If the document changes as the user types — an editor, a live preview — enable incremental rendering so only the edited block is re-processed.

app.config.ts
provideMarkdown({
  incrementalRendering: true,
});

Unchanged blocks keep their DOM nodes, which means the counter above does not reset when you edit an unrelated paragraph. See Incremental rendering for how that works.

Where to go next

On this page