Skip to content

TypeScript Snippets in Astro: Show, Don't Tell

Published: at 

Introduction

Want to show TypeScript code with hover-over type info on your Astro site? Here’s how.

Before You Start

Set up an Astro project. Need help? See the Astro quickstart guide.

Set Up Shiki for Syntax Highlighting

Astro includes Shiki. Let’s configure it:

  1. Edit astro.config.mjs:
import { function defineConfig(config: AstroUserConfig): AstroUserConfig
See the full Astro Configuration API Documentation https://astro.build/config
defineConfig
} from "astro/config";
export default function defineConfig(config: AstroUserConfig): AstroUserConfig
See the full Astro Configuration API Documentation https://astro.build/config
defineConfig
({
AstroUserConfig.markdown?: {
    shikiConfig?: Partial<ShikiConfig>;
    syntaxHighlight?: "shiki" | "prism" | false;
    remarkPlugins?: RemarkPlugins;
    rehypePlugins?: RehypePlugins;
    gfm?: boolean;
    smartypants?: boolean;
    remarkRehype?: RemarkRehype;
} | undefined
@docs@kindheading@nameMarkdown Options
markdown
: {
shikiConfig?: Partial<ShikiConfig> | undefined
@docs@namemarkdown.shikiConfig@typeraw{Partial<ShikiConfig>}@descriptionShiki configuration options. See [the Markdown configuration docs](https://docs.astro.build/en/guides/markdown-content/#shiki-configuration) for usage.
shikiConfig
: {
themes?: Record<string, ThemePresets | ThemeRegistration | ThemeRegistrationRaw> | undefinedthemes: { light: "min-light"light: "min-light", dark: "tokyo-night"dark: "tokyo-night" }, }, }, });
  1. Add a border to code blocks in your CSS:
pre:has(code) {
  @apply border border-skin-line;
}

Add Twoslash for Type Information

  1. Install packages:
npm i -D twoslash twoslash-vue
  1. Update astro.config.mjs:
import { function createTransformerFactory(defaultTwoslasher: TwoslashShikiFunction | TwoslashGenericFunction, defaultRenderer?: TwoslashRenderer): (options?: TransformerTwoslashOptions) => ShikiTransformercreateTransformerFactory, function rendererRich(options?: RendererRichOptions): TwoslashRenderer
An alternative renderer that providers better prefixed class names, with syntax highlight for the info text.
rendererRich
} from '@shikijs/twoslash/core'
import { function createTwoslasher(createOptions?: CreateTwoslashVueOptions): TwoslashInstance
Create a twoslasher instance that add additional support for Vue SFC.
createTwoslasher
} from 'twoslash-vue'
import { function defineConfig(config: AstroUserConfig): AstroUserConfig
See the full Astro Configuration API Documentation https://astro.build/config
defineConfig
} from "astro/config";
export default function defineConfig(config: AstroUserConfig): AstroUserConfig
See the full Astro Configuration API Documentation https://astro.build/config
defineConfig
({
AstroUserConfig.markdown?: {
    shikiConfig?: Partial<ShikiConfig>;
    syntaxHighlight?: "shiki" | "prism" | false;
    remarkPlugins?: RemarkPlugins;
    rehypePlugins?: RehypePlugins;
    gfm?: boolean;
    smartypants?: boolean;
    remarkRehype?: RemarkRehype;
} | undefined
@docs@kindheading@nameMarkdown Options
markdown
: {
shikiConfig?: Partial<ShikiConfig> | undefined
@docs@namemarkdown.shikiConfig@typeraw{Partial<ShikiConfig>}@descriptionShiki configuration options. See [the Markdown configuration docs](https://docs.astro.build/en/guides/markdown-content/#shiki-configuration) for usage.
shikiConfig
: {
themes?: Record<string, ThemePresets | ThemeRegistration | ThemeRegistrationRaw> | undefinedthemes: { light: "min-light"light: "min-light", dark: "tokyo-night"dark: "tokyo-night" }, transformers?: ShikiTransformer[] | undefinedtransformers: [ function createTransformerFactory(defaultTwoslasher: TwoslashShikiFunction | TwoslashGenericFunction, defaultRenderer?: TwoslashRenderer): (options?: TransformerTwoslashOptions) => ShikiTransformercreateTransformerFactory(function createTwoslasher(createOptions?: CreateTwoslashVueOptions): TwoslashInstance
Create a twoslasher instance that add additional support for Vue SFC.
createTwoslasher
())({
TransformerTwoslashOptions.langs?: string[] | undefined
Languages to apply this transformer to
langs
: ['ts', 'tsx', 'vue'],
TransformerTwoslashOptions.renderer?: TwoslashRenderer | undefined
Custom renderers to decide how each info should be rendered
renderer
: function rendererRich(options?: RendererRichOptions): TwoslashRenderer
An alternative renderer that providers better prefixed class names, with syntax highlight for the info text.
rendererRich
({
RendererRichOptions.lang?: string | undefined
Language for syntax highlight.
@defaultthe language of the code block
lang
: 'ts',
}), }), ], }, }, });

Use Interactive Snippets

Create TypeScript snippets in Markdown:

interface User {
  User.name: stringname: string;
  User.age: numberage: number;
}

const const user: Useruser: User = {
  User.name: stringname: "John Doe",
  User.age: numberage: 30
};

var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without calling `require('console')`. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(
const user: User
user
.User.name: stringname);

The // ^? shows where type info appears on hover.

Vue snippets work too:

<script setup lang="ts">
import { function ref<T>(value: T): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which has a single property `.value` that points to the inner value.
@paramvalue - The object to wrap in the ref.@see{@link https://vuejs.org/api/reactivity-core.html#ref}
ref
} from 'vue'
const { const title: stringtitle } =
const defineProps: <{
    title: string;
}>() => DefineProps<LooseRequired<{
    title: string;
}>, never> (+2 overloads)
Vue `<script setup>` compiler macro for declaring component props. The expected argument is the same as the component `props` option. Example runtime declaration: ```js // using Array syntax const props = defineProps(['foo', 'bar']) // using Object syntax const props = defineProps({ foo: String, bar: { type: Number, required: true } }) ``` Equivalent type-based declaration: ```ts // will be compiled into equivalent runtime declarations const props = defineProps<{ foo?: string bar: number }>() ```
@see{@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}This is only usable inside `<script setup>`, is compiled away in the output and should **not** be actually called at runtime.
defineProps
<{
title: stringtitle: string }>() const const count: Ref<number, number>count = ref<number>(value: number): Ref<number, number> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which has a single property `.value` that points to the inner value.
@paramvalue - The object to wrap in the ref.@see{@link https://vuejs.org/api/reactivity-core.html#ref}
ref
(0)
const const increment: () => voidincrement = () => { const count: Ref<number, number>count.
  • value
v
} const const decrement: () => voiddecrement = () => { const count: Ref<number, number>count.Ref<number, number>.value: numbervalue-- } </script> <template> <div: HTMLAttributes & ReservedPropsdiv> <h2: HTMLAttributes & ReservedPropsh2>{{ const title: stringtitle }}</h2: HTMLAttributes & ReservedPropsh2> <p: HTMLAttributes & ReservedPropsp>Count: {{ const count: Ref<number, number>count }}</p: HTMLAttributes & ReservedPropsp> <button: ButtonHTMLAttributes & ReservedPropsbutton @onClick?: ((payload: MouseEvent) => void) | undefinedclick="const increment: () => voidincrement">Increment</button: ButtonHTMLAttributes & ReservedPropsbutton> <button: ButtonHTMLAttributes & ReservedPropsbutton @onClick?: ((payload: MouseEvent) => void) | undefinedclick="const decrement: () => voiddecrement">Decrement</button: ButtonHTMLAttributes & ReservedPropsbutton> </div: HTMLAttributes & ReservedPropsdiv> </template>

For autocompletion, use ^|:

const increment = () => {
  count.v
  //    ^|
}

What You’ve Gained

Your Astro site now has:

Your readers will thank you.