1. Getting Started
  2. Quickstart

Getting Started

Quickstart

In this section we'll get you up and running with KitDocs.

Quick Installation

The following command will scaffold a new SvelteKit application and add all the KitDocs boilerplate for you.

terminal
npm init @svelteness/kit-docs mydocs
Arrow pointing at Skeleton template option when initializing SvelteKit.

Once your application is ready you can skip over to the next steps.

Manual Installation

TIP

See our demo directory on GitHub if you'd like a reference to use as you follow along with the steps below.

  1. Create SvelteKit App

    Create a new SvelteKit application from your terminal (skip this step if you have one). Pick the Skeleton option template.

    terminal
    npm create svelte mydocs
    cd mydocs
    npm i
    
  2. Install Dependencies

    Install KitDocs and all dependencies via NPM.

    terminal
    npm i @svelteness/kit-docs @iconify-json/ri unplugin-icons clsx shiki -D
    
  3. Update Svelte Config

    Add the .md file extension to be processed by Svelte.

    svelte.config.js
    import adapter from '@sveltejs/adapter-auto';
    
    /** @type {import('@sveltejs/kit').Config} */
    const config = {
      extensions: ['.svelte', '.md'],
    
      kit: {
        adapter: adapter(),
      },
    };
    
    export default config;
    
  4. Update Vite Config

    Update your vite.config.js file to match.

    vite.config.js
    import { sveltekit } from '@sveltejs/kit/vite';
    import icons from 'unplugin-icons/vite';
    import kitDocs from '@svelteness/kit-docs/node';
    
    /** @type {import('vite').UserConfig} */
    const config = {
      plugins: [icons({ compiler: 'svelte' }), kitDocs(), sveltekit()],
    };
    
    export default config;
    
  5. Add Global Types

    Add the global TypeScript types to your app.d.ts file.

    src/app.d.ts
    /// <reference types="@sveltejs/kit" />
    /// <reference types="@svelteness/kit-docs/globals" />
    // ...
    
  6. Create KitDocs Endpoints

    Create the KitDocs endpoints that handle markdown meta and sidebar requests.

    src
    └─ routes
       └─ kit-docs
          ├─ [dir].sidebar.json.js
          ├─ [slug].meta.json.js
    
    routes/kit-docs/[slug].meta.json.js
    import { createMetaRequestHandler } from '@svelteness/kit-docs/node';
    
    export const GET = createMetaRequestHandler();
    
    routes/kit-docs/[dir].sidebar.json.js
    import { createSidebarRequestHandler } from '@svelteness/kit-docs/node';
    
    export const GET = createSidebarRequestHandler();
    
  7. Create Layout Files

    Create your layout files. You can change the /docs directory, but remember to update the sidebar loader setting.

    src
    └─ routes
       ├─ __layout-kit-docs.svelte
       └─ docs
          ├─ __layout@kit-docs.svelte
    
    routes/__layout-kit-docs.svelte
    <script context="module">
      export const prerender = true;
    
      export const load = createKitDocsLoader({
        sidebar: {
          '/': null,
          '/docs': '/docs',
        },
      });
    </script>
    
    <script>
      import { page } from '$app/stores';
    
      import { KitDocs, createKitDocsLoader, createSidebarContext } from '@svelteness/kit-docs';
    
      /** @type {import('@svelteness/kit-docs').MarkdownMeta | null} */
      export let meta = null;
    
      /** @type {import('@svelteness/kit-docs').ResolvedSidebarConfig | null} */
      export let sidebar = null;
    
      const { activeCategory } = createSidebarContext(sidebar);
    
      $: category = $activeCategory ? `${$activeCategory}: ` : '';
      $: title = meta ? `${category}${meta.title} | KitDocs` : null;
      $: description = meta?.description;
    </script>
    
    <svelte:head>
      {#key $page.url.pathname}
        {#if title}
          <title>{title}</title>
        {/if}
        {#if description}
          <meta name="description" content={description} />
        {/if}
      {/key}
    </svelte:head>
    
    <KitDocs {meta}>
      <slot />
    </KitDocs>
    
    routes/docs/__layout@kit-docs.svelte
    <!-- This layout inherits from `routes/__layout-kit-docs.svelte` -->
    <!-- Learn more: https://kit.svelte.dev/docs/layouts#named-layouts-inheritance-chains -->
    <slot />
    
  8. Create Markdown File

    Create your first category and markdown file.

    src
    └─ routes
       ├─ __layout-kit-docs.svelte
       └─ docs
          ├─ __layout@kit-docs.svelte
          └─ [...1]first-category
             ├─ [...1]hello-world.md
    
    [...1]hello-world.md
    ---
    description: My first markdown file.
    ---
    
    # Hello World
    
    {$frontmatter.description}
    
    <script>
      console.log('Markdown files are Svelte components!');
    </script>
    

You should now be able to start your dev server by running npm run dev. Visit /docs/first-category/hello-world to see the Markdown content. Open the developer console and confirm that the string 'Markdown files are Svelte components!' has logged.

Congratulations, core setup is done 🎉

Home Page

You can create a Markdown (routes/index@kit-docs.md) or Svelte (routes/index@kit-docs.svelte) file at the root of your routes directory if you'd like to include a home page.

In some cases you might want to use the first page of your docs as the home page. You can achieve this by creating a index.svelte file at the root of your routes directory with the following content:

src
└─ routes
   ├─ index.svelte <-
routes/index.svelte
<script context="module">
  export const prerender = true;

  /** @type {import("@sveltejs/kit").Load} */
  export function load() {
    return {
      status: 307,
      redirect: '/docs/first-category/first-page',
    };
  }
</script>

Tailwind

You'll need to do the following if you're using Tailwind and don't plan on using the default theme to ensure default markdown rules (e.g., CodeFence, Admonition, Steps) work as expected.

First, add the default markdown components to your content config:

tailwind.config.cjs
module.exports = {
  content: [
    './src/**/*.{html,svelte}',
    './node_modules/@svelteness/kit-docs/client/kit-docs/**/*.svelte',
  ],
};

Finally, copy and adjust our theme and variants plugin settings into your tailwind.config.cjs file. Refer to our CSS variables file to get any values.

Meta Endpoint

The kit-docs/[slug].meta.json.js endpoint will match the slug parameter to a Markdown file in routes/, parse it and return Markdown metadata such as the title, description, frontmatter, headings, etc. This endpoint is required.

src
└─ routes
   └─ kit-docs
      ├─ [slug].meta.json.js <-

Resolving

You can override the default slug resolver which handles mapping a slug to a markdown file in the routes directory like so:

[slug].meta.json.js
import { createMetaRequestHandler } from '@svelteness/kit-docs/node';

export const GET = createMetaRequestHandler({
  // map slug to absolute or relative file path to `routes` directory.
  // returning `null` or `undefined` will fallback to default resolver.
  // `resolve` helper will return default value.
  resolve: (slug, { resolve }) => resolve(slug),

  // you can provide an array and first to resolve will be accepted.
  resolve: [(slug) => ``, null, undefined, false, async (slug) => null],
});

Transforming

You can transform the meta object before it's returned like so:

[slug].meta.json.js
import { createMetaRequestHandler } from '@svelteness/kit-docs/node';

export const GET = createMetaRequestHandler({
  transform: ({ slug, filePath, meta, html, links }) => {
    meta.title = '...';
    meta.description = '...';
    meta.frontmatter.prop = '...';
    meta.headers = [
      ...meta.headers,
      { level: 2, title: 'Custom Heading', slug: '#custom-heading' },
      // ...
    ];
  },

  // you can provide an array and they will all be called.
  transform: [({ slug }) => {}, null, undefined, false, async () => {}],
});

You can also return a transformer when resolving a slug like so:

[slug].meta.json.js
import { createMetaRequestHandler } from '@svelteness/kit-docs/node';

export const GET = createMetaRequestHandler({
  resolve: (slug, { resolve }) => {
    return {
      file: resolve(slug),
      // this can also be an array of transformers.
      transform: ({ slug, filePath, meta }) => {
        // ...
      },
    };
  },
});

Filtering

You can configure which files are included and excluded like so:

[slug].meta.json.js
import { createMetaRequestHandler } from '@svelteness/kit-docs/node';

export const GET = createMetaRequestHandler({
  // These are Rollup filter patterns.
  // Filters match against file paths relative to `src/routes` dir.
  // Paths are cleaned. No rest params `[...1]` or layout id `@...`.
  include: /\.md/,
  exclude: ['/docs/**/file.md', /some-regex/, '**/ignored-dir'],
});

For reference, here's the FilterPattern type:

/**
 * A valid `picomatch` glob pattern, or array of patterns.
 */
export type FilterPattern = ReadonlyArray<string | RegExp> | string | RegExp | null;

Handler

You can call the meta request handler and handle the result yourself like so:

[slug].meta.json.js
import {
  handleMetaRequest,
  paramToSlug,
} from '@svelteness/kit-docs/node';

/** @type {import('@sveltejs/kit').RequestHandler} */
export function get({ params }) {
  try {
    const slug = paramToSlug(params.slug);
    const parserResult = await handleMetaRequest(slug);
    return { body: parserResult.meta };
  } catch (e) {
    // no-op
  }

  return { body: null };
}

The kit-docs/[dir].sidebar.json.js endpoint will match the dir parameter to a directory in routes/, read all the files inside and return a sidebar config object. You can skip this endpoint and create the sidebar manually.

src
└─ routes
   └─ kit-docs
      ├─ [dir].sidebar.json.js <-

The loaded sidebar config object looks something like this:

const sidebar = {
  links: {
    'First Category': [
      { title: 'First Page', slug: '/docs/first-category/first-page' },
      { title: 'Second Page', slug: '/docs/first-category/second-page' },
    ],
    'Second Category': [
      // ...
    ],
  },
};

Page Ordering

SvelteKit rest parameters are 'non-greedy' and can be used to match 0 or more route segments. We can use them with KitDocs to order our routes so that the sidebar configuration is built in the right order. Here's an example of how you can use rest params to organise your docs:

src
└─ routes
   └─ docs
      └─ [...1]first-category
         ├─ [...1]first-page.md
         ├─ [...2]second-page.md
      └─ [...2]second-category
         ├─ [...1]first-page.md
         ├─ [...2]second-page.md

Based on the directory structure above, the sidebar endpoint will return the following slugs:

  • /docs/first-category/first-page
  • /docs/first-category/second-page
  • /docs/second-category/first-page
  • /docs/second-category/second-page

Deep Match

Deep matching is useful when you have nested paths that belong to a single sidebar item. For example, the Tailwind docs has a page at /docs/installation that contains tabbed links to various installation types such as:

  • /docs/installation/using-postcss
  • /docs/installation/framework-guides
  • /docs/installation/play-cdn

You can achieve this structure like so:

src
└─ routes
   └─ docs
      └─ [...1]getting-started
         └─ [...1_deep]installation <- Deep match.
            ├─ index.md
            ├─ using-postcss.md
            ├─ framework-guides.md
            ├─ play-cdn.md

The sidebar will now include a single entry at Getting Started > Installation. Keep in mind that a deep match means all nested files other than index.md will be ignored.

Resolving

You can override any of the default resolvers like so:

[dir].sidebar.json.js
import { createSidebarRequestHandler } from '@svelteness/kit-docs/node';

export const GET = createSidebarRequestHandler({
  // `data` includes file paths, frontmatter, file content and more.
  // returning `null` or `undefined` will fallback to default resolver.
  // the `resolve` helper will return the default value.
  resolveTitle: (data) => ``,
  resolveCategory: (data) => ``,
  resolveSlug: ({ resolve }) => resolve(),
});

The sidebar title is inferred in the following order:

  1. Check if resolveTitle option returns value.
  2. Check for a sidebar_title frontmatter property.
  3. Check for a title frontmatter property.
  4. Try to extract a heading # Heading.
  5. Map the filename from kebab-case to title-case (e.g., my-file -> My File ).
---
sidebar_title: Custom Sidebar Title
---

Filtering

You can configure which files are included and excluded like so:

[dir].sidebar.json.js
import { createSidebarRequestHandler } from '@svelteness/kit-docs/node';

export const GET = createSidebarRequestHandler({
  // These are Rollup filter patterns.
  // Filters match against file paths relative to `src/routes` dir.
  // Paths are cleaned. No rest params `[...1]` or layout id `@...`.
  include: /\.md/,
  exclude: ['/docs/**/file.md', /some-regex/, '**/ignored-dir'],
});

For reference, here's the FilterPattern type:

/**
 * A valid `picomatch` glob pattern, or array of patterns.
 */
export type FilterPattern = ReadonlyArray<string | RegExp> | string | RegExp | null;

Category Names

You can control how the category names are formatted like so:

[dir].sidebar.json.js
import { createSidebarRequestHandler } from '@svelteness/kit-docs/node';

export const GET = createSidebarRequestHandler({
  // Default formatter maps `kebab-case` to `Title Case`.
  formatCategoryName: (name, { format }) => format(name),
});

Handler

You can call the sidebar request handler and handle the result yourself like so:

[dir].sidebar.json.js
import {
  handleSidebarRequest,
  paramToDir,
} from '@svelteness/kit-docs/node';

/** @type {import('@sveltejs/kit').RequestHandler} */
export function get({ params }) {
  try {
    const dir = paramToDir(params.dir);
    const sidebar = await handleSidebarRequest(dir, {
      filter: (id) => id.endsWith('.md')
    });
    return { body: sidebar };
  } catch (e) {
    // no-op
  }

  return { body: null };
}

Loaders

We provide a SvelteKit loader for your convenience to simplify loading Markdown related data into your application.

<script context="module">
  import { createKitDocsLoader } from '@svelteness/kit-docs';

  export const load = createKitDocsLoader({ sidebar: '/docs' });
</script>

The sidebar configuration option should point to a directory relative to src/routes. You can provide either a string as shown above, or a multi-path config like so:

<script context="module">
  import { createKitDocsLoader } from '@svelteness/kit-docs';

  export const load = createKitDocsLoader({
    sidebar: {
      '/': null,
      '/docs': '/docs',
      '/docs/faq': '/faq',
    }
  });
</script>

A multi-path configuration will try to match the key to the current path. If matched, the sidebar will be built from the matching directory. For example, the routes/faq directory will be used to build the sidebar if the path starts with /docs/faq.

Loader Functions

You can load the data yourself if required like so:

Page.svelte
<script context="module">
  import { loadKitDocsMeta, loadKitDocsSidebar } from '@svelteness/kit-docs';

  /** @type {import('@sveltejs/kit').Load} */
  export async function load({ url, fetch }) {
    const meta = await loadKitDocsMeta(url.pathname, { fetch });
    const sidebar = await loadKitDocsSidebar('/docs', { url, fetch });
    return {
      props: {
        meta,
        sidebar,
      }
    }
  }
</script>

Frontmatter

Any Markdown file that contains a YAML frontmatter block will be processed by gray-matter. The frontmatter must be at the top of the Markdown file, and must take the form of valid YAML set between triple-dashed lines.

---
title: Page Title
description: Page description.
---

# {$frontmatter.title}

{$frontmatter.description}

...

Stores

You can import the kitDocs or frontmatter stores for accessing Markdown metadata like so:

<script>
  import { kitDocs, frontmatter } from '@svelteness/kit-docs';

  $: console.log($kitDocs.meta);

  // shorthand for $kitDocs.meta.frontmatter
  $: console.log($frontmatter);
</script>

Global Components

KitDocs will import components in the src/kit-docs directory into every single Markdown file and also map them to markdown containers.

src
└─ kit-docs <- Components in this directory are "global".
   ├─ Button.svelte

Now, inside any markdown file you can use the <Button> component like so:

Component.md
<!-- Use the component as-is. -->
<Button />

<!-- Or, use a markdown container. -->
:::button propA="valueA"|propB=10
Default slot content here.
:::

<!-- You can pass in dynamic slot elements! -->
<!-- If you omit `tag`, it'll default to `<p>`. -->
:::button (tag=h1&slot=title)=Title
Default slot content here.
:::
TIP

You can change the global components directory in your plugin settings.