storyloco
v1.14.5
Published
A collection of slick Storyblok field plugins built with Svelte 5, TypeScript, and Tailwind CSS
Maintainers
Readme
Storyloco
A collection of slick Storyblok field plugins built with Svelte 5, TypeScript, and Tailwind CSS. Each plugin is designed to be lightweight, performant, and a joy to use.
🚀 What's Included
Field Plugins
- 🎬 Mux Video - Upload, manage, and configure videos with Mux integration
- 📎 Asset Plus - Enhanced asset picker with search, filters, and previews
- 📝 Heading - Simple heading editor with level selection (H1-H6)
- 🎨 Theme - Color theme selector with visual previews
- 🔗 Link - Link editor with support for internal, external, email, and asset links
- 🔍 SEO - SEO metadata editor with support for title, description, OG tags, and Twitter cards
- 🏗️ Plans - Floor plan manager with drag-and-drop sorting for types, floors, dimensions, and rooms
- 📝 Input - Comprehensive form input field with support for all HTML input types, checkboxes, radio buttons, selects, and textareas
- ⏰ Time - Simple time input field with step control
Vite Plugins
- 📋 Storyblok Schema - Automatically generate TypeScript types from your Storyblok components
- 🔄 Storyblok Redirects - Generate and handle redirects from Storyblok datasource entries
Storyblok Client
- 🎯 StoryblokClient - Reactive wrapper for Storyblok with Svelte 5 integration
- 🔧 Utilities - Re-exported
editable,Block, andrichtextfunctions
Shared Components
A comprehensive UI component library built with:
- Svelte 5 - Latest reactive framework
- Tailwind CSS - Utility-first styling
- Bits UI - Headless component primitives
- TypeScript - Full type safety
📦 Installation
bun add storyloco🎯 Usage
Import Types
// Import specific plugin types
import type { Video } from 'storyloco/mux'
import type { Heading } from 'storyloco/heading'
import type { Link } from 'storyloco/link'
import type { Input } from 'storyloco/input'
import type { Asset } from 'storyloco/asset'
import type { SEO } from 'storyloco/seo'
// Or import everything
import type { Video, Heading, Link, Input, Asset, SEO } from 'storyloco'Use Storyblok Client
1. Create your Storyblok client
$lib/storyblok.ts:
import { StoryblokClient } from 'storyloco'
import { PUBLIC_STORYBLOK_ACCESS_TOKEN } from '$env/static/public'
// Create client (defaults to $lib/blocks/*.svelte) - init() called automatically
export const client = new StoryblokClient(PUBLIC_STORYBLOK_ACCESS_TOKEN)
// With custom component globs
export const client = new StoryblokClient(
PUBLIC_STORYBLOK_ACCESS_TOKEN,
import.meta.glob(['$blocks/*.svelte', '$templates/*.svelte'])
)
// Single glob pattern
export const client = new StoryblokClient(PUBLIC_STORYBLOK_ACCESS_TOKEN, import.meta.glob('$blocks/*.svelte'))2. Use in your Svelte components
+page.svelte:
<script lang="ts">
import { Block } from 'storyloco'
import { client } from '$lib/storyblok.js'
let { data: _data } = $props()
// Connect story for live preview updates
const data = $derived(client.connect(_data.story))
</script>
<!-- Render blocks -->
{#each data.story?.content.blocks || [] as blok}
<Block {blok} />
{/each}3. Create your component structure
$lib/blocks/hero.svelte:
<script lang="ts">
import { editable } from 'storyloco'
// This is auto-generated by the Storyblok Schema plugin
import type { Blok, Hero } from '$lib/components.schema.js'
interface Props {
blok: Blok<Hero>
}
let { blok } = $props()
</script>
<section use:editable={blok}>
<h1>{blok.headline}</h1>
<p>{blok.subheadline}</p>
</section>Use Shared Components
import { Input, Label, Switch, Skeleton, Select, Separator } from 'storyloco/shared'
import { cn } from 'storyloco/shared/utils'Vite Plugins
Storyblok Schema Plugin
Automatically generate TypeScript types from your Storyblok components during development:
// vite.config.ts
import { schema } from 'storyloco/vite'
export default defineConfig({
plugins: [
schema({
output_path: 'src/lib',
interval_ms: 60_000 // regenerate every minute
})
]
})Options:
storyblok_personal_access_token- Your Storyblok personal access token (defaults toSTORYBLOK_PERSONAL_ACCESS_TOKENenv var)storyblok_space_id- Your Storyblok space ID (defaults toSTORYBLOK_SPACE_IDenv var)output_path- Where to output generated files (defaults tosrc/lib)filename- Name of the generated file (defaults tocomponents.schema.ts)interval_ms- How often to regenerate types (defaults to 60000ms)
The plugin will create a file called components.schema.ts (auto-generated) in the src/lib directory. This file will be updated automatically when you make changes to your Storyblok components as per the interval specified.
Custom Plugin Types:
You can define custom types for Storyblok field plugins by exporting an interface in your vite config:
// vite.config.ts
import { schema } from 'storyloco/vite'
export interface StoryblokCustomPlugins {
'theme-field': string
'heading-field': import('storyloco/heading').Heading
'loco-link-field': import('storyloco/link').Link
'mux-field': import('storyloco/mux').Video
'loco-input': import('storyloco/input').Input
'seo-metatags': {
plugin: 'seo_metatags'
_uid: import('crypto').UUID
title?: string
og_image?: string
og_title?: string
description?: string
twitter_image?: string
twitter_title: string
og_description: string
twitter_description: string
}
'loco-time': string
'loco-asset': import('storyloco/asset').Asset
}
export default defineConfig({
plugins: [
schema({
output_path: 'src/lib',
interval_ms: 60000
})
]
})The plugin will:
- Pull your component schema from Storyblok
- Generate TypeScript definitions with proper types
- Use your custom plugin types when available
- Format the output with Prettier
- Lock the generated file to prevent manual edits
- Regenerate automatically when components change
Storyblok Redirects Plugin
Generate and handle redirects from Storyblok datasource entries:
// vite.config.ts
import { redirects } from 'storyloco/vite'
export default defineConfig({
plugins: [
redirects({
datasource: 'redirects',
public_storyblok_access_token: 'your-token'
})
]
})Options:
datasource- The Storyblok datasource name (defaults to 'redirects')public_storyblok_access_token- Your public access token (defaults toPUBLIC_STORYBLOK_ACCESS_TOKENenv var)
SvelteKit Integration:
// src/hooks.server.ts
import { handle_redirects } from 'storyloco/vite'
export const handle = handle_redirectsThe plugin will:
- Fetch redirect entries from your Storyblok datasource
- Generate a redirects map during build
- Support exact matches and wildcard patterns
- Handle both internal and external redirects
🔧 Development
Prerequisites
- Bun (recommended) or Node.js 18+
- Storyblok account for field plugin development
Setup
# Clone the repo
git clone <your-repo-url>
cd storyloco
# Install dependencies
bun install
# Start development
bun run devProject Structure
storyloco/
├── packages/
│ ├── mux/ # Video upload & management plugin
│ ├── heading/ # Heading editor plugin
│ ├── theme/ # Theme selector plugin
│ ├── link/ # Link editor plugin
│ ├── seo/ # SEO metadata editor plugin
│ ├── input/ # Form input field plugin
│ └── shared/ # UI component library
├── src/
│ └── index.ts # Type exports
└── package.json # Root workspace configAdding New Plugins
Create plugin package:
bun run add-pluginExport types in your plugin:
Create a
types.tsfile in your plugin package:// packages/your-plugin/types.ts export interface YourType { // your interface here }Add to root exports in
package.json:{ "exports": { "./your-plugin": { "types": "./packages/your-plugin/types.ts", "import": "./packages/your-plugin/src/app.svelte" } } }Re-export in
src/index.ts:export type { YourType } from '../packages/your-plugin/types'
🎨 Shared Components
The shared package provides a comprehensive set of UI components:
Form Components
Input- Text input with proper stylingLabel- Accessible form labelsSwitch- Toggle switch componentSelect- Select componentSeparator- Separator component
Display Components
Skeleton- Loading skeleton component
Utilities
cn()- Class name utility (clsx + tailwind-merge)setup()- Global setup function
🔌 Field Plugin Development
Each plugin follows the Storyblok field plugin pattern:
<script lang="ts">
import { createFieldPlugin, type FieldPluginResponse } from '@storyblok/field-plugin'
interface YourData {
// your data structure
}
type Plugin = FieldPluginResponse<YourData | null>
let plugin: Plugin | null = $state(null)
let content: YourData = $state(initial_value)
onMount(() => {
createFieldPlugin({
enablePortalModal: true,
validateContent: (c) => {
// validation logic
return { content: validated_content }
},
onUpdateState: (state) => {
plugin = state as Plugin
},
})
})
</script>🚀 Deployment
Deploy Individual Plugins
# From the plugin directory
cd packages/mux
bun run deployBuild Shared Package
cd packages/shared
bun run build🛠️ Tech Stack
- Framework: Svelte 5 with TypeScript
- Styling: Tailwind CSS
- Components: Shadcn Svelte, Bits UI primitives
- Build Tool: Vite
- Package Manager: Bun (recommended)
- Linting: ESLint + Prettier
📄 License
MIT License - feel free to use this however you want, you absolute legend! 🎯
🤝 Contributing
- Fork the repo
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Built with ❤️ and a lot of swearing by the Storyloco team
