Vanilla Toolkit Docs

Documentation for the Vanilla Toolkit template


Project maintained by renierr Hosted on GitHub Pages — Theme by mattgraham

Vanilla Toolkit

Preview

Minimalistic, lightning-fast tool collection
Vite + TypeScript + Tailwind – no React, no framework

Features

Create a new tool (30 seconds)

Create a folder inside src/tools/. The folder name becomes the tool’s path/URL slug.

src/tools/my-tool/
├── config.json     # Name + description + configuration
├── template.html   # Your layout
└── index.ts        # Your logic (optional)

1) Add config.json

Minimal example:

{
  "name": "My Tool",
  "description": "Does something useful",
  "draft": false,
  "example": false
}

Notes:

Optional fields you can add later:

2) Add template.html

This is the tool’s UI. Keep it small and composable (cards, inputs, buttons).

Practical tips:

3) Add behavior in index.ts (optional)

If your tool is interactive, put the logic in index.ts. Typical responsibilities:

Keep it defensive:

Export style

Your tool entry can be exported either as a default export or as a named init export:

// Default export
export default function init() {
  // ...
}

// Named export
export function init() {
  // ...
}

Important: cleanup when navigating between tools

Tools can be opened/closed via routing, so your index.ts may run multiple times. If you attach any global listeners (e.g. document.addEventListener, window.addEventListener), timers (setInterval), observers, etc.,
make sure you return a cleanup function that removes them.

export default function init() { 
    const onKeyDown = (e: KeyboardEvent) => { 
        // ... 
    };
    document.addEventListener('keydown', onKeyDown);
    
    // Return cleanup to prevent duplicate listeners when navigating away/back 
    return () => { 
        document.removeEventListener('keydown', onKeyDown); 
    }; 
}

Rule of thumb:

4) Run it

Start the dev server and open the app:

pnpm run dev

Your tool should appear automatically on the overview page. If it doesn’t:

Common patterns (quick checklist)

Tool-specific dependencies (pnpm-workspace.yaml)

Each tool can declare its own dependencies by adding a package.json inside its folder.
This is supported by the project’s pnpm-workspace.yaml setup.

Example:
The tool example-package in this project add its own dependencies:
(demo purpose only with a lightweight dependency)

// src/tools/example-package/package.json

{
  "name": "example-package",
  "version": "1.0.0",
  "dependencies": {
    "is-odd": "3.0.1"
  }
}

Note: This allows tools to use different libraries or versions as needed, without polluting the main project dependencies.

Optional: src/main.ts (custom startup invocation)

In addition to per-tool scripts, you can add an optional project-level entry hook: src/main.ts.

If the file exists, it will be auto-imported and executed once on startupbefore the initial route (overview/tool) is rendered.
This is useful for global, one-time setup such as:

Export shape

You can provide either a default export or a named init export. Both may be async:

// src/main.ts 
import type { CustomMainContext } from './js/types';

export default function main(ctx: CustomMainContext) { 
    console.log('Loaded tools:', ctx.tools.length); 
    // global setup... 
}

// alternatively: 
export function init(ctx: CustomMainContext) { 
    // ... 
}

Context (ctx)

Currently, the context contains the already-discovered tool list:

Important note about side effects

This main.ts invocation is a one-time hook (not a routing lifecycle).
If you register global side effects here (e.g. window.addEventListener, timers, observers), you are responsible for managing cleanup yourself — unlike tool index.ts, which can return a cleanup function.

Ordering & Section grouping (Overview page)

Tools can be sorted and grouped into sections on the overview page by adding two optional fields to a tool’s config.json:

Example config.json

{ 
  "name": "My Tool", 
  "description": "Does something useful", 
  "draft": false, 
  "example": false, 
  "sectionId": "examples", 
  "order": 1
}

How sorting works

This means you can keep the list stable and intentional, even when multiple tools share the same order.

How sections work

Configure section titles via SiteConfig

Section titles and descriptions live in the site configuration.

  1. Copy the template config:
    • src/config/site.config.template.tssrc/config/site.config.ts
  2. Define your sections (keys are the sectionIds):
export const siteConfig = { 
  // ... 
  toolSections: { 
      examples: { title: 'Examples', description: 'Demo tools that show how the template works.', }, 
      general: { title: 'General', description: 'Everyday helpers and utilities.', }, 
  }, 
};

Section order:
Sections are rendered in the insertion order of toolSections first, followed by any additional sections discovered at runtime.

Site configuration override

The default configuration lives in src/config/site.config.template.ts.
To customize the configuration for your project, copy the file to the Name site.config.ts and change any configuration values. See types in src/config/site.config.ts for possible values.

Tool Icons (Lucide)

Each tool can optionally define an icon in its config.json.

Use the icon id syntax from lucide (lower case with dashes)

If icon is missing or unknown, a default icon is used.

Available icon ids

per default all lucide icons are included. You can add additional icons registering them at startup (see src/main.ts).

Register custom icons (derived projects)

This template exposes an icon registry so derived projects can add (or override) icon IDs without editing src/js/tool-icons.ts.

1) Import registerToolIcons in your entry file (e.g. src/script.ts).

2) Import any additional icons you want from lucide or any other source follwing the syntax.

3) Register them at once during startup (see main.ts hook above).

import { registerToolIcons } from 
        './src/js/tool-icons';
import { ArrowLeft } from '@lucide/icons';
    
registerToolIcons({
    ArrowLeft: ArrowLeft,
    // add more icons here
});

Now you can reference your new icon IDs from any tool config.json:

{ 
  "name": "My Tool", 
  "description": "Does something useful", 
  "icon": "arrow-left"
}

Notes:


Template Placeholders

Brief and practical:

Example (Template → Result):

<!-- Template -->
<h1>{{ config.title }}</h1>

<!-- After replacement -->
<h1>Vanilla Toolkit</h1>

Dark/Light Mode

This project works with Tailwind’s class strategy but also supports daisyUI’s theme system. In practice prefer daisyUI theme tokens and components instead of sprinkling many dark: utilities across your templates.

Why prefer daisyUI tokens?

Quick daisyUI examples (concise):

<!-- Card -->
<div class="card bg-base-100 shadow-md p-4">
  <h3 class="text-lg font-semibold">Card title</h3>
  <p class="text-sm text-base-content/70">Card content</p>
</div>

<!-- Button -->
<button class="btn btn-primary">Save</button>

<!-- Input -->
<div class="form-control">
  <label class="label"><span class="label-text">Name</span></label>
  <input class="input input-bordered" type="text" />
</div>

Theme-aware tokens (preferred replacements for common pairs):

Toggling theme (simple script):

// set theme to 'dark' or 'light' (or any daisyUI theme name)
document.documentElement.setAttribute('data-theme', 'dark');
// read current theme
const theme = document.documentElement.getAttribute('data-theme');

When to still use dark:

Rule of thumb:

Focus & Hover States with daisyUI

Most components include sensible focus/hover styles. If you need custom behavior, combine tokens with Tailwind utilities:

<input class="input input-bordered focus:ring-2 focus:ring-primary/60" aria-label="Example input" />
<button class="btn btn-primary hover:brightness-90">Action</button>

Custom styles

Add your own custom styles to src/css/styles.css below the marker comment to avoid conflicts with the template styles on merge.


Extending SiteContext (derived projects)

This template is meant to be cloned (GitHub template). To allow project-specific context fields without modifying the core template types, SiteContext exposes a TypeScript declaration merging extension point.

What you can extend

SiteContext automatically includes everything you add to the global interface SiteContextCustom.

How to use it in your cloned project

1) Create a declaration file (any name is fine), for example:

2) Add your custom fields by extending SiteContextCustom:

declare global {
    interface SiteContextCustom {
        custom?: { foo: string; bar?: number; };
    }
}
export {};

After this, your SiteContext type will include custom, features, etc. everywhere it’s used.
You can now use it in your tool configs and templates.

Notes / troubleshooting


Keeping Derived Projects Up-to-Date (Template Sync Workflow)

This template supports automatic updates for derived repositories using a GitHub Actions workflow. The workflow uses AndreasAugustin/actions-template-sync to regularly or manually sync changes from the template repository into your project.

Workflow Setup

The synchronization uses the existing workflow file .github/workflows/template-sync.yml.
This workflow is configured to run automatically at 00:00 UTC on the first day of every month (cron: '0 0 1 * *').
You can also trigger it manually at any time via the GitHub Actions UI.

How it works

Ignoring files during sync

To prevent certain files or folders from being overwritten, a .templatesyncignore exist in the .github directory.
Use glob patterns to specify files to ignore.


And above all, have fun with this template! 😊