sv-page-builder Svelte Themes

Sv Page Builder

A powerful, modern drag-and-drop page builder library for Svelte 5 applications. Built with TypeScript, featuring a premium dark UI inspired by Webflow and Framer.

sv-page-builder

A powerful, modern drag-and-drop page builder library for Svelte 5 applications. Built with TypeScript, featuring a premium dark UI inspired by Webflow and Framer.


✨ Features

šŸŽØ Visual Editor

  • Premium Dark UI – Webflow/Framer-inspired design with modern glassmorphism effects
  • WYSIWYG Editing – Real-time preview with instant visual feedback
  • Responsive Viewports – Desktop, Tablet, and Mobile preview with device frames
  • Component Tree – Hierarchical layer panel with expand/collapse and drag reordering
  • RTL Support – Full support for Arabic and other right-to-left languages

🧱 Component System

  • 13+ Built-in Components – Container, Text, Heading, Button, Image, Link, Divider, Spacer, Input, Textarea, Select, Checkbox, Repeater
  • Drag & Drop – Add components from palette to canvas, reorder via drag
  • Inline Editing – Double-click text components to edit content directly
  • External Nodes – Register custom components from your application
  • Node Overrides – Customize built-in components without replacing them

šŸŽÆ Styling & Layout

  • Visual Style Editor – Typography, spacing, colors, borders, shadows, effects
  • Responsive Styles – Per-breakpoint style overrides (mobile, tablet, desktop)
  • Flexbox & Grid – Visual layout controls with intuitive UI
  • Custom Code – Inject scoped CSS and JavaScript per component
  • Color Picker – Full HSL color wheel with hex input

šŸ“Š Data Binding

  • Data Sources – Connect to REST APIs or use static data
  • Visual Binding – Bind component props to data paths with schema browser
  • Repeater Component – Render lists with pagination support
  • Lazy Loading – Only fetch data that's actually used in the document
  • Custom Loaders – Per-source loading skeletons

⚔ Productivity

  • Undo/Redo – Full history with keyboard shortcuts
  • Keyboard Shortcuts – Delete, Duplicate, Copy/Paste, and more
  • JSON Import/Export – Save and load page designs
  • i18n Ready – Built-in English and Arabic translations, extensible

šŸ“¦ Installation (Private Package)

This is a private package. Use one of the following methods to install in your projects:

Add directly from your Git repository:

# Using SSH (recommended for private repos)
npm install git+ssh://[email protected]:aashahin/sv-page-builder.git

# Or using HTTPS with token
npm install git+https://<TOKEN>@github.com/aashahin/sv-page-builder.git

# With bun
bun add git+ssh://[email protected]:aashahin/sv-page-builder.git

Or add to your package.json:

{
  "dependencies": {
    "@aashahin/sv-page-builder": "git+ssh://[email protected]:aashahin/sv-page-builder.git"
  }
}

To pin a specific version/tag/branch:

{
  "dependencies": {
    "@aashahin/sv-page-builder": "git+ssh://[email protected]:aashahin/sv-page-builder.git#v1.0.0"
  }
}

Method 2: Local File Path

For local development or monorepo setups:

{
  "dependencies": {
    "@aashahin/sv-page-builder": "file:../page-builder"
  }
}

Or use npm/bun link:

# In the page-builder directory
bun link

# In your other project
bun link @aashahin/sv-page-builder

Method 3: GitHub Packages (Optional)

If you want to publish to GitHub's private registry:

  1. Create .npmrc in your project root:

    @aashahin:registry=https://npm.pkg.github.com
    //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
    
  2. Build and publish:

    # In page-builder
    bun run build:package
    npm publish --registry=https://npm.pkg.github.com
    
  3. Install in other projects:

    npm install @aashahin/sv-page-builder
    

Peer Dependencies

This library requires Svelte 5 as a peer dependency:

{
  "peerDependencies": {
    "svelte": "^5.0.0"
  }
}

šŸš€ Quick Start

Basic Usage

<script lang="ts">
  import { PageBuilder, type PageDocument, type Locale } from '@aashahin/sv-page-builder';
  import '@aashahin/sv-page-builder/styles';

  let locale: Locale = 'en';

  function handleSave(doc: PageDocument) {
    console.log('Document saved:', doc);
    // Save to your backend
  }
</script>

<PageBuilder {locale} onSave={handleSave} />

With Initial Document

<script lang="ts">
  import { PageBuilder, type PageDocument } from '@aashahin/sv-page-builder';
  import '@aashahin/sv-page-builder/styles';

  // Load from your backend
  const initialDocument: PageDocument = {
    id: 'page-1',
    title: 'My Page',
    root: {
      id: 'root',
      type: 'container',
      props: {},
      styles: { padding: '16px' },
      children: []
    },
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString()
  };
</script>

<PageBuilder {initialDocument} onSave={(doc) => saveToBackend(doc)} />

Rendering Pages (Production)

Use PageRenderer to display saved pages without the editor UI:

<script lang="ts">
  import { PageRenderer, type PageDocument } from '@aashahin/sv-page-builder';
  import '@aashahin/sv-page-builder/styles';

  // Load from your backend
  let document: PageDocument = /* ... */;
</script>

<PageRenderer {document} />

šŸŽ›ļø PageBuilder Props

Prop Type Default Description
locale 'en' | 'ar' 'en' Current locale for translations
initialDocument PageDocument – Initial document to load
onDocumentChange (doc: PageDocument) => void – Callback when document changes
onSave (doc: PageDocument) => void – Callback when save is triggered (Ctrl+S)
class string – Additional CSS class
canvasStyle string – Inline style for canvas (e.g., custom fonts)
externalNodes ExternalNodeConfig[] [] Custom component definitions
disableBuiltInNodes boolean false Hide all built-in components
nodeOverrides Record<ComponentType, NodeOverride> {} Override built-in component definitions
brandName string 'Page Builder' Brand name shown in toolbar
brandLink string – Link for the brand name
dataSources DataSource[] [] External data sources for binding
autoFetchData boolean false Fetch all data sources on mount

🧩 Built-in Components

Layout

Component Description Accepts Children
Container Flexbox/Grid container for grouping elements āœ…
Repeater Renders child template for each item in a data source āœ…

Content

Component Description Key Props
Text Paragraph text with inline editing content
Heading H1-H6 headings content, level
Button Clickable button content, variant, size
Link Anchor element content, href, target
Image Image with fit options src, alt, objectFit
Divider Horizontal rule thickness, style
Spacer Vertical spacing height

Form

Component Description Key Props
Input Text input field placeholder, type, required
Textarea Multi-line input placeholder, rows
Select Dropdown select options, placeholder
Checkbox Checkbox input label, checked

āŒØļø Keyboard Shortcuts

Shortcut Action
A or / Open block picker
Delete / Backspace Delete selected component
Ctrl + D Duplicate component
Ctrl + C Copy component
Ctrl + V Paste component
Ctrl + Z Undo
Ctrl + Shift + Z Redo
Ctrl + S Save
Escape Deselect / Close modal / Exit preview
Enter Start editing text component

šŸ”Œ External Nodes (Custom Components)

Register your own components to appear in the block picker:

<script lang="ts">
  import { PageBuilder, type ExternalNodeConfig } from '@aashahin/sv-page-builder';
  import MyVideoPlayer from './MyVideoPlayer.svelte';

  const externalNodes: ExternalNodeConfig[] = [
    {
      type: 'video-player',           // Unique type identifier
      label: 'Video Player',          // Display name
      icon: 'Video',                  // Lucide icon name
      category: 'media',              // Category in palette
      acceptsChildren: false,
      defaultProps: {
        videoUrl: '',
        autoplay: false
      },
      propsSchema: [
        { key: 'videoUrl', label: 'Video URL', type: 'text' },
        { key: 'autoplay', label: 'Autoplay', type: 'boolean' }
      ],
      component: MyVideoPlayer
    }
  ];
</script>

<PageBuilder {externalNodes} />

Creating External Node Components

External nodes receive node and isEditMode props:

<!-- MyVideoPlayer.svelte -->
<script lang="ts">
  import type { ComponentNode } from '@aashahin/sv-page-builder';

  interface Props {
    node: ComponentNode;
    isEditMode?: boolean;
  }

  let { node, isEditMode = true }: Props = $props();

  const videoUrl = $derived((node.props.videoUrl as string) || '');
  const autoplay = $derived((node.props.autoplay as boolean) || false);
</script>

{#if isEditMode}
  <div class="video-placeholder">
    <span>šŸŽ¬ Video: {videoUrl || 'No URL set'}</span>
  </div>
{:else}
  <video src={videoUrl} {autoplay} controls></video>
{/if}

Custom Categories

const externalNodes: ExternalNodeConfig[] = [
  {
    type: 'chart',
    label: 'Chart',
    icon: 'BarChart',
    category: 'analytics',           // Custom category
    categoryLabel: 'Analytics',      // Category header text
    categoryIcon: 'TrendingUp',      // Category header icon
    // ...
  }
];

ExternalNodeConfig Interface

Property Type Description
type string Unique identifier (must not conflict with built-in types)
label string Display name in block picker
icon string Lucide icon name
category string Category: 'layout', 'content', 'media', 'interactive', 'custom', or custom
categoryLabel string? Localized label for category header
categoryIcon string? Icon for category header
acceptsChildren boolean Whether component can contain children
defaultProps object Default prop values for new instances
defaultStyles object Default style values
propsSchema PropSchema[] Property panel schema
component SvelteComponent The Svelte component to render

šŸŽØ Node Overrides

Customize built-in nodes without replacing them:

<script lang="ts">
  import { PageBuilder, type NodeOverride, type ComponentType } from '@aashahin/sv-page-builder';
  import MediaGalleryPicker from './MediaGalleryPicker.svelte';

  const nodeOverrides: Partial<Record<ComponentType, NodeOverride>> = {
    image: {
      // Use custom media picker instead of text input for src
      propsSchemaOverrides: {
        src: {
          type: 'custom',
          component: MediaGalleryPicker
        }
      }
    },
    button: {
      // Change default button props
      defaultProps: {
        content: 'Get Started',
        variant: 'primary'
      }
    }
  };
</script>

<PageBuilder {nodeOverrides} />

Custom Property Editors

Create custom editors for any prop using type: 'custom':

<!-- MediaGalleryPicker.svelte -->
<script lang="ts">
  import type { CustomPropComponentProps } from '@aashahin/sv-page-builder';

  let { value, onChange, label }: CustomPropComponentProps = $props();

  async function openGallery() {
    const selected = await myMediaGallery.open();
    if (selected) {
      onChange(selected.url);
    }
  }
</script>

<div>
  <label>{label}</label>
  <button onclick={openGallery}>
    {value ? 'Change Image' : 'Select Image'}
  </button>
  {#if value}
    <img src={value as string} alt="Preview" />
  {/if}
</div>

šŸ“Š Data Sources

Basic Usage

<script lang="ts">
  import { PageBuilder, type DataSource } from '@aashahin/sv-page-builder';

  const dataSources: DataSource[] = [
    // REST API
    {
      id: 'products',
      name: 'Products',
      type: 'rest',
      config: {
        url: 'https://api.example.com/products',
        method: 'GET',
        responsePath: 'data',      // Extract from response
        cacheTtl: 60000            // Cache for 1 minute
      },
      schema: {
        type: 'array',
        fields: [
          { key: 'id', label: 'ID', type: 'number' },
          { key: 'name', label: 'Name', type: 'string' },
          { key: 'price', label: 'Price', type: 'number' },
          { key: 'image', label: 'Image', type: 'image' }
        ]
      }
    },
    // Static data
    {
      id: 'company',
      name: 'Company Info',
      type: 'static',
      config: {
        data: {
          name: 'Acme Corp',
          tagline: 'Innovation at its finest'
        }
      },
      schema: {
        type: 'object',
        fields: [
          { key: 'name', label: 'Company Name', type: 'string' },
          { key: 'tagline', label: 'Tagline', type: 'string' }
        ]
      }
    }
  ];
</script>

<PageBuilder {dataSources} />

Binding Data to Components

When editing a component, users can bind props to data sources. Bindings are stored as:

{
  "content": {
    "$bind": {
      "sourceId": "company",
      "path": "tagline",
      "fallback": "Loading..."
    }
  }
}

Custom Loading Components

<script lang="ts">
  import { PageBuilder, type DataSource, type DataSourceLoadingProps } from '@aashahin/sv-page-builder';
  import ProductSkeleton from './ProductSkeleton.svelte';

  const dataSources: DataSource[] = [
    {
      id: 'products',
      name: 'Products',
      type: 'rest',
      config: { url: '/api/products' },
      loadingComponent: ProductSkeleton  // Custom skeleton
    }
  ];
</script>

PageRenderer with Loading Slot

<script lang="ts">
  import { PageRenderer, type PageDocument, type DataSource } from '@aashahin/sv-page-builder';

  let document: PageDocument = /* ... */;
  let dataSources: DataSource[] = /* ... */;
</script>

<PageRenderer {document} {dataSources}>
  {#snippet loadingSlot({ sources })}
    <div class="loading-overlay">
      <p>Loading {sources.length} source(s)...</p>
      {#each sources as source}
        <span>{source.sourceName}</span>
      {/each}
    </div>
  {/snippet}
</PageRenderer>

DataSource Interface

Property Type Description
id string Unique identifier
name string Display name in UI
type 'rest' | 'static' Data source type
config RestConfig | StaticConfig Type-specific configuration
schema DataSchema? Schema for binding editor autocomplete
description string? Description shown in UI
icon string? Lucide icon name
loadingComponent Component? Custom loading skeleton

REST Config Options

Property Type Description
url string API endpoint URL
method 'GET' | 'POST' | ... HTTP method
headers Record<string, string>? Request headers
body unknown? Request body
responsePath string? JSONPath to extract data
cacheTtl number? Cache TTL in milliseconds
labelField string? Field for item labels (item browser)
thumbnailField string? Field for thumbnails (item browser)

🌐 Internationalization (i18n)

Built-in Locales

The library includes English (en) and Arabic (ar) translations with RTL support.

<PageBuilder locale="ar" />

Using Translation Functions

<script lang="ts">
  import { useTranslation, useDirection, useLocale } from '@aashahin/sv-page-builder';

  const t = useTranslation();
  const direction = useDirection();
  const locale = useLocale();
</script>

<p dir={direction}>{t('common.save')}</p>

Creating Custom Translations

import { createI18nStore, type TranslationDictionary } from '@aashahin/sv-page-builder';

const fr: TranslationDictionary = {
  common: {
    save: 'Enregistrer',
    cancel: 'Annuler',
    // ...
  },
  // ...
};

šŸ“ Project Structure

src/lib/
ā”œā”€ā”€ builder/
│   ā”œā”€ā”€ stores/           # Svelte stores (page, selection, history, viewport)
│   ā”œā”€ā”€ registry/         # Component definitions
│   ā”œā”€ā”€ data/             # Data sources module
│   ā”œā”€ā”€ utils/            # Utility functions (tree, id, overrides)
│   ā”œā”€ā”€ actions/          # Svelte actions (customCode)
│   ā”œā”€ā”€ external.ts       # External nodes API
│   └── types.ts          # TypeScript types
ā”œā”€ā”€ components/
│   ā”œā”€ā”€ PageBuilder.svelte    # Main entry component
│   ā”œā”€ā”€ builder/              # Canvas, ComponentRenderer, ComponentWrapper
│   ā”œā”€ā”€ nodes/                # Node implementations (TextNode, ImageNode, etc.)
│   ā”œā”€ā”€ palette/              # Block picker modal
│   ā”œā”€ā”€ properties/           # Properties panel tabs
│   ā”œā”€ā”€ renderer/             # PageRenderer for production
│   ā”œā”€ā”€ toolbar/              # Top toolbar
│   ā”œā”€ā”€ tree/                 # Component tree/layers panel
│   └── ui/                   # Shared UI components (Select, ColorPicker)
ā”œā”€ā”€ i18n/
│   ā”œā”€ā”€ translations/         # en.ts, ar.ts
│   ā”œā”€ā”€ index.svelte.ts       # i18n store and context
│   └── types.ts              # Translation types
ā”œā”€ā”€ styles/
│   └── layout.css            # Design system (Tailwind v4)
└── index.ts                  # Public exports

šŸ”§ Development

Prerequisites

  • Node.js 18+ or Bun
  • npm, pnpm, yarn, or bun

Getting Started

# Clone the repository
git clone https://github.com/aashahin/sv-page-builder.git
cd sv-page-builder

# Install dependencies
bun install

# Start development server
bun dev

The demo app will be available at http://localhost:5173

Scripts

Script Description
bun dev Start development server
bun run build Build the demo app
bun run build:package Build the library for publishing
bun run check Run type checking
bun run format Format code with Prettier
bun run lint Check formatting

Building the Library

bun run build:package

This outputs to dist/ with:

  • ESM JavaScript files
  • TypeScript declarations
  • CSS styles

šŸ“¤ Exports

The library exports the following:

Components

  • PageBuilder – Main editor component
  • PageRenderer – Production renderer (no editor UI)
  • Canvas, ComponentRenderer, ComponentWrapper – Builder internals
  • ComponentPalette, BlocksPalette – Block picker components
  • PropertiesPanel – Properties panel
  • ComponentTree – Layer tree
  • Toolbar – Top toolbar

Stores & Context

  • createPageStore, setPageContext, getPageContext
  • createSelectionStore, setSelectionContext, getSelectionContext
  • createHistoryStore, setHistoryContext, getHistoryContext
  • createViewportStore, setViewportContext, getViewportContext
  • createDataSourcesStore, setDataSourcesContext, getDataSourcesContext
  • createExternalNodesStore, setExternalNodesContext, getExternalNodesContext
  • createOverridesStore, setOverridesContext, getOverridesContext
  • createI18nStore, setI18nContext, getI18nContext

Types

  • ComponentNode, ComponentType, ComponentId, ComponentDefinition
  • PageDocument, StyleProperties, ResponsiveStyles
  • PropSchema, CustomPropComponentProps, NodeOverride
  • DataSource, DataBinding, DataSchema
  • ExternalNodeConfig, ExternalNodeProps
  • Locale, Direction, TranslationDictionary, I18nStore
  • ViewportBreakpoint, ViewportConfig, DragData

Utilities

  • generateId, generatePageId
  • findComponentById, findParentById, cloneTree, cloneDocument
  • addComponent, removeComponent, updateComponent, moveComponent
  • scopeCustomCss, executeCustomJs
  • isDataBinding, createBinding, resolveBinding

šŸŽØ Styling

The library uses Tailwind CSS v4 with a custom design system. Import the styles:

import '@aashahin/sv-page-builder/styles';

CSS Custom Properties

The design system defines CSS variables for colors, typography, spacing, etc.:

:root {
  --color-bg-base: oklch(13.5% 0 0);
  --color-bg-elevated: oklch(18% 0 0);
  --color-accent-500: oklch(59% 0.18 250);
  --color-text-primary: oklch(96% 0 0);
  /* ... */
}

Customizing Canvas Fonts

Pass custom styles to the canvas:

<PageBuilder canvasStyle="font-family: 'Your Font', sans-serif;" />

šŸ“ TypeScript

This library is written in strict TypeScript. All types are exported for use in your application:

import type {
  PageDocument,
  ComponentNode,
  DataSource,
  ExternalNodeConfig
} from '@aashahin/sv-page-builder';

šŸ¤ Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

šŸ“„ License

MIT Ā© Abdelrahman Shaheen


Built with ā¤ļø using Svelte 5 & TypeScript

Top categories

Loading Svelte Themes