Last Updated: December 27, 2025 Silex Version: 3.6.0-canary.1 Purpose: Reference documentation for understanding and extending the dashboard
- Quick Start
- Running Services
- Project Structure
- Complete Data Flow
- Key Integration Points
- Template System
- Special Techniques
- Implementation Guide
- Template/Fork Feature Design
cd /home/lexoyo/_/Silex/packages/silex-dashboard
# Start all services (TinaCMS, Silex, 11ty)
npm start
# OR start individually:
npm run tina:dev # TinaCMS on :4001
npm run silex:dev # Silex on :6805
npm run 11ty:dev # 11ty on :8080- Silex Editor: http://localhost:6805/?id=dashboard
- TinaCMS Admin: http://localhost:4001/admin
- 11ty Preview: http://localhost:8080/en/
- GraphQL Playground: http://localhost:4001/graphql
┌─────────────────┐
│ TinaCMS │ Port 4001
│ GraphQL API │ (Content Management)
└────────┬────────┘
│
├─────────────────────────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 11ty │ Port 8080 │ Silex │ Port 6805
│ Static Gen │ (Preview) │ Editor │ (Design)
└─────────────────┘ └─────────────────┘
│ │
│ │
▼ ▼
┌─────────────────────────────────────────────────┐
│ _site/ (Static Output) │
│ - /en/index.html - /fr/index.html │
│ - /en/connectors/ - /fr/connectors/ │
└─────────────────────────────────────────────────┘
| Service | Port | Purpose | Technologies |
|---|---|---|---|
| TinaCMS | 4001 | Content management, GraphQL API | TinaCMS, GraphQL |
| Silex | 6805 | Visual editor, design, page settings | GrapesJS, Express |
| 11ty | 8080 | Static site generation, templating | Eleventy, Liquid |
silex-dashboard/
├── silex/ # Silex editor data
│ ├── websites/
│ │ └── dashboard/
│ │ ├── website.json # ★ Pages, components, styles (2 pages, 133 styles)
│ │ └── meta.json # Site metadata {"name":"Dashboard"}
│ ├── server-config.js # Express middleware (locale, routing)
│ └── client-config.js # CMS datasource config
│
├── templates/ # 11ty input (Silex output)
│ ├── websites.html # Base template (Silex-generated)
│ ├── websites-en.html # ★ 11ty template with frontmatter (EN)
│ ├── websites-fr.html # ★ 11ty template with frontmatter (FR)
│ ├── connectors.html # Base template
│ ├── connectors-en.html # ★ 11ty template (EN)
│ ├── connectors-fr.html # ★ 11ty template (FR)
│ ├── css/ # Silex-generated CSS
│ └── assets/ # Images, fonts, favicon
│
├── 11ty/ # 11ty configuration
│ ├── eleventy.config.mjs # Main config (i18n, passthroughs)
│ ├── _data/
│ │ ├── api-translations.json # Error messages, UI strings
│ │ └── site.js # Global site data
│ └── _includes/
│ └── alternate.liquid # Hreflang tags for SEO
│
├── collections/ # ★ TinaCMS content
│ ├── home/
│ │ ├── en.md # English UI labels (title, buttons, etc.)
│ │ └── fr.md # French UI labels
│ ├── connectors/
│ │ ├── en.md # Connectors page labels
│ │ └── fr.md
│ ├── languages/
│ │ ├── en.json # Language config {"code":"en","label":"English"}
│ │ └── fr.json
│ └── settings/
│ ├── en.json # Nav, footer links
│ └── fr.json
│
├── tina/ # TinaCMS config
│ ├── config.ts # ★ Schema definition (4 collections)
│ └── __generated__/ # Auto-generated GraphQL schema
│
└── _site/ # 11ty output (gitignored)
├── en/
│ ├── index.html # Final websites page (EN)
│ └── connectors/
│ └── index.html # Final connectors page (EN)
├── fr/
│ ├── index.html # Final websites page (FR)
│ └── connectors/
│ └── index.html # Final connectors page (FR)
├── css/ # Copied CSS
├── js/ # Vue.js runtime
└── assets/ # Copied assets
2 Pages in Silex (defined in website.json):
| Page ID | Name | Purpose | Permalink |
|---|---|---|---|
mk3OKgfr4A9V7Dww |
Websites | Main dashboard (list user's websites) | /{{lang}}/ |
BOCWuSXKn6FRo8x5L |
Connectors | Login/authentication page | /{{lang}}/connectors/ |
Page Multiplication for i18n:
- Each Silex page → 2 language versions (EN, FR)
- Total: 4 final HTML files
Designer opens: http://localhost:6805/?id=dashboard
↓
Designs pages visually:
- Drag & drop components
- Apply CSS styles
- Add Vue.js directives (v-if, v-for, v-on:click)
↓
Configures page settings:
- General: Page name = "Websites"
- CMS: Permalink = /{{lang}}/
- CMS: Language list = "en,fr"
- Code: <head> content = Vue.js app
- SEO: Title expression (GraphQL query)
↓
Clicks "Publish"
↓
Silex generates templates/websites.html
↓
Saves to silex/websites/dashboard/website.json
website.json structure:
{
"pages": [
{
"id": "mk3OKgfr4A9V7Dww",
"name": "Websites",
"settings": {
"silexLanguagesList": "en,fr",
"eleventyPermalink": "[{...}]",
"head": "/* Vue.js code */"
}
}
],
"styles": [ /* 133 CSS rules */ ],
"components": [],
"assets": [ /* images, fonts */ ]
}Content editor opens: http://localhost:4001/admin
↓
Edits collections:
- home/en.md: title, subtitle, button labels
- home/fr.md: titre, sous-titre, étiquettes
- settings/en.json: navigation, footer links
↓
Saves changes
↓
TinaCMS writes to collections/ directory
↓
TinaCMS GraphQL API auto-updates
TinaCMS Schema (tina/config.ts):
{
collections: [
{
name: "home",
path: "collections/home",
fields: [
{ label: 'Title', name: 'title', type: 'string' },
{ label: 'Subtitle', name: 'subtitle', type: 'string' },
{ label: 'Add Button', name: 'add_button', type: 'string' },
// ... 20+ fields
]
},
// ... 3 more collections (connectors, languages, settings)
]
}11ty watches templates/ directory
↓
Detects websites.html changed
↓
For each language (en, fr):
↓
1. Creates template file: websites-en.html
- Adds frontmatter (permalink, lang, collection)
- Keeps all HTML/CSS/JS from Silex
↓
2. Executes websites-en.11tydata.mjs:
- Fetches TinaCMS GraphQL (homeConnection, settingsConnection)
- Returns { tina: { ... } }
↓
3. Processes Liquid tags in HTML:
{% assign var = tina.homeConnection.edges
| where: "node.lang", "en"
| first %}
{{ var.node.title }}
↓
4. Outputs _site/en/index.html
↓
Done! Static HTML ready
11tydata.mjs example:
export default async function (configData) {
const response = await fetch('http://localhost:4001/graphql', {
method: 'POST',
body: JSON.stringify({
query: `{
homeConnection {
edges {
node {
title
subtitle
add_button
lang
}
}
}
settingsConnection {
edges {
node {
nav { label, url, target }
lang
}
}
}
}`
})
})
const json = await response.json()
return {
tina: json.data,
lang: 'en'
}
}User visits: http://localhost:8080/en/
↓
Browser loads HTML with embedded Vue.js in <head>
↓
Vue app initializes on window.load:
↓
1. const { api } = silex
2. const user = await api.getUser()
3. if (user) {
this.websites = await api.websiteList()
} else {
redirect to /en/connectors/
}
↓
Vue reactivity kicks in:
- v-if="!empty" shows/hides sections
- v-for="website in websites" renders list
- v-on:click="createWebsite" handles clicks
↓
User sees fully interactive dashboard!
Vue.js app structure (in page settings head):
const App = {
data() {
return {
websites: [], // From Silex API
user: null, // From Silex API
loading: true, // UI state
error: null, // Error messages
showCreationForm: false // UI state
}
},
async mounted() {
const user = await getUser({ type: ConnectorType.STORAGE })
if (user) {
this.user = user
this.websites = await websiteList({
connectorId: user.storage.connectorId
})
}
},
methods: {
async createWebsite() {
await websiteCreate({
websiteId: toSafeId(this.newWebsiteName),
data: { name: this.newWebsiteName }
})
this.websites = await websiteList()
},
async deleteWebsite(websiteId) {
if (confirm('Are you sure?')) {
await websiteDelete({ websiteId })
this.websites = await websiteList()
}
}
}
}
createApp(App).mount('.app')How Language Multiplication Works:
-
Silex Page Settings:
{ name: 'Websites', silexLanguagesList: 'en,fr', // ← Triggers multiplication eleventyPermalink: '[{ "type":"property", "fieldId":"fixed", "options":{"value":"/{{lang}}/"} }]' }
-
Silex Publishes →
templates/websites.html(base template, no frontmatter) -
11ty Detects
silexLanguagesListand creates:templates/websites-en.htmltemplates/websites-fr.html
-
Each Gets Frontmatter:
--- permalink: "/{{lang}}/" # From eleventyPermalink expression lang: "en" # Added by language multiplication collection: "Websites" # From page name ---
-
11ty Processes each template independently with its own language context
Expression System:
Silex uses a JSON-based expression builder that converts to Liquid syntax.
Example Expression (in page settings SEO Title):
[
{"type":"property","fieldId":"homeConnection"},
{"type":"property","fieldId":"edges"},
{"type":"filter","id":"where","options":{"key":"node.lang","value":"en"}},
{"type":"filter","id":"first"},
{"type":"property","fieldId":"node"},
{"type":"property","fieldId":"title"}
]Converts to Liquid:
{% assign var_title = tina.homeConnection.edges
| where: "node.lang", page.lang
| first %}
{{ var_title.node.title }}Used in HTML:
<h1>
{% assign var = tina.homeConnection.edges
| where: "node.lang", page.lang
| first %}
{{ var.node.title }}
</h1>
<!-- Outputs: <h1>Silex Dashboard</h1> -->How Vue.js is Embedded:
-
In Page Settings (Code tab):
<script src="/js/vue.global.prod.js"></script> <script src="/js/main.js"></script> <script type="module"> const App = { /* Vue app definition */ } createApp(App).mount('.app') </script>
-
In Silex Visual Editor, add Vue directives to HTML elements:
<div v-if="!empty" class="section"> <h1 v-text="title"></h1> <button v-on:click="createWebsite">Create</button> </div> <div v-for="(website, index) in websites" :key="index"> <h3 v-text="website.name"></h3> <p v-text="'Updated ' + new Date(website.updatedAt).toLocaleDateString()"></p> </div>
-
11ty Preserves all Vue directives (they're just HTML attributes)
-
Browser Executes Vue.js at runtime, making the page interactive
Static Content (from TinaCMS via Liquid):
<button>
{% assign var = tina.homeConnection.edges | where: "node.lang", "en" | first %}
{{ var.node.add_button }}
</button>
<!-- Outputs: <button>Create website</button> -->Dynamic Content (from Vue.js):
<div v-for="website in websites">
<h3 v-text="website.name"></h3>
<!-- Outputs: <h3>My Project</h3> (from Silex API at runtime) -->
</div>Why Both?
- TinaCMS: UI labels, navigation, footer (changes rarely, SEO-friendly, static)
- Vue.js: User data, forms, API calls (changes frequently, requires authentication)
┌─────────────────────────────────────────────────────────┐
│ Silex Editor (1 page) │
│ - name: "Websites" │
│ - silexLanguagesList: "en,fr" │
└────────────────────┬────────────────────────────────────┘
│ Publish
▼
┌─────────────────────────────────────────────────────────┐
│ templates/websites.html (base) │
│ - No frontmatter │
│ - All HTML/CSS/JS from Silex │
└────────────────────┬────────────────────────────────────┘
│ 11ty detects silexLanguagesList
▼
┌───────────┴───────────┐
▼ ▼
┌────────────────┐ ┌────────────────┐
│ websites- │ │ websites- │
│ en.html │ │ fr.html │
├────────────────┤ ├────────────────┤
│ --- │ │ --- │
│ permalink: │ │ permalink: │
│ "/en/" │ │ "/fr/" │
│ lang: "en" │ │ lang: "fr" │
│ --- │ │ --- │
│ │ │ │
│ (same HTML) │ │ (same HTML) │
└────────┬───────┘ └────────┬───────┘
│ │
│ 11ty + Liquid │
▼ ▼
┌────────────────┐ ┌────────────────┐
│ _site/en/ │ │ _site/fr/ │
│ index.html │ │ index.html │
│ │ │ │
│ (English │ │ (French │
│ content) │ │ content) │
└────────────────┘ └────────────────┘
Template has Liquid tag:
{{ tina.homeConnection.edges | where: "node.lang", page.lang | first | node.title }}
▲
│
│
For websites-en.html: For websites-fr.html:
page.lang = "en" page.lang = "fr"
│ │
▼ ▼
Filters to en.md content Filters to fr.md content
│ │
▼ ▼
Outputs: "Silex Dashboard" Outputs: "Tableau de bord Silex"
Silex Editor (Design)
↓ publishes
templates/websites.html ────┐
│ copied + frontmatter added
├──→ templates/websites-en.html
└──→ templates/websites-fr.html
↓ 11ty + TinaCMS data
↓
┌──→ _site/en/index.html
└──→ _site/fr/index.html
Problem: Prevent flash of unstyled content before Vue.js loads
Solution: CSS-based progressive enhancement
/* In templates (added by Silex) */
.before-js > * {
visibility: hidden;
opacity: 0;
transition: opacity .5s ease;
}
.after-js > * {
visibility: visible;
opacity: 1;
}
.before-js:before {
content: 'Loading';
position: absolute;
top: 49%;
left: 49%;
}<!-- Body has before-js class initially -->
<body class="app before-js">
<!-- Content hidden until Vue loads -->
</body>// After Vue mounts
setTimeout(() => {
document.querySelector('.before-js').classList.add('after-js')
}, 100)Problem: Better UX while data is loading
Solution: Show placeholder skeletons with CSS animation
<!-- Loading state -->
<div v-if="loading" class="skeleton-anim">
<h3 class="skeleton-text skeleton">Loading...</h3>
<p class="skeleton-text skeleton">Loading...</p>
</div>
<!-- Loaded state -->
<div v-if="!loading" v-for="website in websites">
<h3 v-text="website.name"></h3>
<p v-text="website.updatedAt"></p>
</div>.skeleton-anim:after {
content: "";
position: absolute;
background: linear-gradient(
0.25turn,
transparent,
rgba(255,255,255,.75),
transparent
);
animation: loading 1.5s infinite;
}
@keyframes loading {
to { background-position: 200% 0; }
}Visual Builder for GraphQL Queries:
Instead of writing Liquid manually, Silex provides a dropdown UI:
Field Selector:
┌─────────────────────────────┐
│ homeConnection [▼] │ ← Dropdown shows all GraphQL fields
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ edges [▼] │
└─────────────────────────────┘
↓
Filter:
┌─────────────────────────────┐
│ where [▼] │
│ key: node.lang │
│ value: en │
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ first [▼] │
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ node.title [▼] │
└─────────────────────────────┘
Converts to:
{{ tina.homeConnection.edges | where: "node.lang", "en" | first | node.title }}Stored in website.json as:
{
"eleventySeoTitle": "[
{\"type\":\"property\",\"fieldId\":\"homeConnection\"},
{\"type\":\"property\",\"fieldId\":\"edges\"},
{\"type\":\"filter\",\"id\":\"where\",\"options\":{\"key\":\"node.lang\",\"value\":\"en\"}},
{\"type\":\"filter\",\"id\":\"first\"},
{\"type\":\"property\",\"fieldId\":\"node\"},
{\"type\":\"property\",\"fieldId\":\"title\"}
]"
}Problem: Translate API error messages
Solution: Centralized translation file + Liquid interpolation
File: 11ty/_data/api-translations.json
{
"en": {
"Failed to start dashboard": "Failed to start dashboard",
"Website created successfully": "Website created successfully"
},
"fr": {
"Failed to start dashboard": "Erreur, impossible de démarrer",
"Website created successfully": "Le site a bien été créé"
}
}Usage in Vue.js code:
this.error = `{{ api-translations[lang]["Failed to start dashboard"] }} - ${error.message}`
this.message = '{{ api-translations[lang]["Website created successfully"] }}'After 11ty processing:
// In en/index.html
this.error = `Failed to start dashboard - ${error.message}`
// In fr/index.html
this.error = `Erreur, impossible de démarrer - ${error.message}`Server-side routing (silex/server-config.js):
// Detect language from browser
router.use(locale(languages.map(l => l.code), 'en'))
// Redirect to user's language
router.use('/', (req, res, next) => {
if (req.path === '/' && !req.query.id) {
res.redirect(`/${req.locale}/`) // → /en/ or /fr/
} else {
next()
}
})Client-side routing (in Vue.js):
openLogin() {
const path = `/{{lang}}/connectors/`
window.open(path, '_self')
}
openEditor(id) {
window.open(`/?id=${id}&lang={{lang}}&connectorId=${this.user.storage.connectorId}`, '_self')
}Interactive buttons without JavaScript:
.fx-scale-round {
position: relative;
overflow: hidden;
transition: transform 0.2s cubic-bezier(0, -0.530, 0.405, 2.8);
}
.fx-scale-round::after {
content: "";
background: #ffffff;
position: absolute;
border-radius: 50%;
left: -50%;
right: -50%;
top: -100%;
bottom: -100%;
transform: scale(0);
transition: all 0.3s ease-out;
}
.fx-scale-round:hover {
transform: scale(1.1);
}
.fx-scale-round:hover::after {
transform: scale(1);
}-
In Silex Editor:
- Click "+" in Pages panel - Name: "Settings" - Design the page visually - Configure page settings: - General: name = "Settings" - CMS: permalink = /{{lang}}/settings/ - Code: Add Vue.js if needed - Click "Publish" -
In TinaCMS (if page needs content):
// tina/config.ts { name: "settings_content", path: "collections/settings_content", fields: [ { name: 'title', type: 'string' }, // ... more fields ] }
-
Create data file:
// templates/settings-en.11tydata.mjs export default async function() { const response = await fetch('http://localhost:4001/graphql', { body: JSON.stringify({ query: `{ settingsContentConnection { ... } }` }) }) return { tina: response.data } }
-
11ty auto-generates:
_site/en/settings/index.html_site/fr/settings/index.html
-
Update schema (
tina/config.ts):{ name: "home", fields: [ // ... existing fields { label: 'Welcome Message', name: 'welcome_message', type: 'string' } ] }
-
Restart TinaCMS:
npm run tina:dev
-
Edit content in TinaCMS admin:
- Open http://localhost:4001/admin
- Edit
home/en.md - Add value for "Welcome Message"
-
Use in template (via expression builder or Liquid):
{{ tina.homeConnection.edges | where: "node.lang", "en" | first | node.welcome_message }}
-
Open page settings in Silex (Code tab)
-
Add method:
methods: { // ... existing methods async archiveWebsite(websiteId) { this.loading = true try { await websiteArchive({ websiteId, connectorId: this.user.storage.connectorId }) this.websites = await websiteList({ connectorId: this.user.storage.connectorId }) this.message = 'Website archived successfully' } catch (error) { this.error = `Failed to archive website - ${error.message}` } this.loading = false } }
-
Add button in Silex visual editor:
<button v-on:click="archiveWebsite(website.websiteId)"> Archive </button>
-
Publish and test
Option 1: Visual Editor
- Select element in Silex
- Use Style Manager panel (right side)
- Apply styles visually
Option 2: CSS Classes
- Add class in Silex:
my-custom-button - Define in page settings (Code tab):
<style> .my-custom-button { background: linear-gradient(45deg, #8873fe, #ff6b9d); border-radius: 8px; padding: 12px 24px; } </style>
Option 3: Global Styles
- Edit
templates/websites.html<style>section - Changes apply to all language versions
-
Designer wants to share a template:
- "Export my dashboard design as a reusable template"
- "Let others start from my design"
-
User wants to clone a site:
- "Create a copy of this dashboard for another project"
- "Start from a template instead of blank"
-
Agency wants to offer templates:
- "Pre-built dashboards for clients"
- "Customizable starting points"
A complete, self-contained Silex website package:
template-dashboard/
├── metadata.json # Template info (name, author, preview)
├── website.json # Silex pages, styles, components
├── collections/ # TinaCMS content structure
├── tina-schema.json # TinaCMS schema (exported)
├── assets/ # Images, fonts
└── config/
├── client-config.js # CMS datasource config
├── server-config.js # Server middleware (optional)
└── eleventy.config.mjs # 11ty config (optional)
{
"id": "dashboard-v1",
"name": "Dashboard Template",
"description": "Multi-language dashboard with user management",
"author": "Silex Labs",
"version": "1.0.0",
"preview": "preview.png",
"tags": ["dashboard", "admin", "i18n", "vue"],
"technologies": ["TinaCMS", "Vue.js", "11ty"],
"languages": ["en", "fr"],
"pages": 2,
"features": [
"User authentication",
"Website CRUD",
"Multi-language support",
"Responsive design"
],
"customization": {
"colors": ["primary", "secondary", "background"],
"texts": ["site_name", "tagline"],
"logo": true
}
}Export Flow:
1. User clicks "Export as Template" in Silex
2. Silex packages:
- website.json (from current site)
- collections/ (from disk)
- tina/config.ts → tina-schema.json
- assets/ (images, fonts)
- metadata.json (user fills form)
3. Downloads template-dashboard.zip
Import Flow:
1. User clicks "Import Template" in Silex
2. Uploads template-dashboard.zip
3. Silex extracts and creates:
- New website ID
- Copies website.json to silex/websites/{newId}/
- Copies collections/ to project
- Sets up TinaCMS schema
- Imports assets
4. Opens new site in editor
Implementation:
// In Silex server
router.post('/api/template/export', async (req, res) => {
const { websiteId } = req.body
// Read website.json
const website = await readWebsite(websiteId)
// Package files
const zip = new JSZip()
zip.file('website.json', JSON.stringify(website))
zip.file('metadata.json', JSON.stringify(req.body.metadata))
// Add collections
const collections = await fs.readdir('collections')
for (const col of collections) {
const files = await fs.readdir(`collections/${col}`)
for (const file of files) {
const content = await fs.readFile(`collections/${col}/${file}`)
zip.file(`collections/${col}/${file}`, content)
}
}
// Add assets
const assets = website.assets
for (const asset of assets) {
const file = await fs.readFile(`templates/${asset.src}`)
zip.file(`assets/${asset.src}`, file)
}
// Generate zip
const buffer = await zip.generateAsync({ type: 'nodebuffer' })
res.setHeader('Content-Disposition', `attachment; filename=${websiteId}.zip`)
res.send(buffer)
})
router.post('/api/template/import', upload.single('template'), async (req, res) => {
const zip = await JSZip.loadAsync(req.file.buffer)
// Extract metadata
const metadata = JSON.parse(await zip.file('metadata.json').async('string'))
// Generate new website ID
const newId = generateId()
// Extract website.json
const website = JSON.parse(await zip.file('website.json').async('string'))
await writeWebsite(newId, website)
// Extract collections
const collectionFiles = Object.keys(zip.files).filter(f => f.startsWith('collections/'))
for (const file of collectionFiles) {
const content = await zip.file(file).async('string')
await fs.writeFile(file, content)
}
// Extract assets
const assetFiles = Object.keys(zip.files).filter(f => f.startsWith('assets/'))
for (const file of assetFiles) {
const content = await zip.file(file).async('nodebuffer')
await fs.writeFile(`templates/${file}`, content)
}
res.json({ websiteId: newId, name: metadata.name })
})Template Registry:
// In Silex server
const TEMPLATES = [
{
id: 'dashboard',
name: 'Dashboard',
path: './templates/dashboard-template'
},
{
id: 'portfolio',
name: 'Portfolio',
path: './templates/portfolio-template'
}
]
router.get('/api/templates', (req, res) => {
res.json(TEMPLATES.map(t => ({
id: t.id,
...require(`${t.path}/metadata.json`)
})))
})
router.post('/api/website/create-from-template', async (req, res) => {
const { templateId, websiteId, customizations } = req.body
const template = TEMPLATES.find(t => t.id === templateId)
// Load template
const website = require(`${template.path}/website.json`)
// Apply customizations
if (customizations.colors) {
applyColorCustomizations(website, customizations.colors)
}
if (customizations.texts) {
applyTextCustomizations(website, customizations.texts)
}
// Save new website
await writeWebsite(websiteId, website)
// Copy collections
await copyDir(`${template.path}/collections`, 'collections')
res.json({ websiteId })
})UI Flow:
1. User clicks "New Website from Template"
2. Shows template gallery:
┌─────────────┬─────────────┬─────────────┐
│ Dashboard │ Portfolio │ Blog │
│ [Preview] │ [Preview] │ [Preview] │
│ [Use] │ [Use] │ [Use] │
└─────────────┴─────────────┴─────────────┘
3. User clicks "Use Dashboard"
4. Customization dialog:
- Site name: [My Dashboard____]
- Primary color: [#8873fe]
- Logo: [Upload___]
5. Creates website, opens in editor
Simple duplication (already possible in dashboard):
// In Vue.js app
async duplicateWebsite(websiteId) {
await websiteDuplicate({
websiteId,
connectorId: this.user.storage.connectorId
})
this.websites = await websiteList()
}Enhanced with customization:
async forkWebsite(websiteId) {
// Show customization dialog
const customizations = await showForkDialog()
// Duplicate
const newId = await websiteDuplicate({ websiteId })
// Apply customizations via API
await websiteCustomize({
websiteId: newId,
name: customizations.name,
colors: customizations.colors,
logo: customizations.logo
})
// Open in editor
window.open(`/?id=${newId}`, '_self')
}Define customizable fields:
// In template metadata
{
"customization": {
"schema": [
{
"id": "primary_color",
"label": "Primary Color",
"type": "color",
"default": "#8873fe",
"affects": [".button--primary", ".nav__item--active"]
},
{
"id": "site_name",
"label": "Site Name",
"type": "text",
"default": "My Dashboard",
"affects": ["collections/home/*/title"]
},
{
"id": "logo",
"label": "Logo",
"type": "image",
"default": "/assets/logo.svg",
"affects": [".nav__logo"]
}
]
}
}Apply customizations:
function applyCustomizations(website, customizations) {
// Replace colors in styles
website.styles.forEach(style => {
if (style.style['background-color'] === '#8873fe') {
style.style['background-color'] = customizations.primary_color
}
})
// Replace text in collections
collections.home.forEach(file => {
file.title = file.title.replace('My Dashboard', customizations.site_name)
})
// Replace logo
website.assets.forEach(asset => {
if (asset.src === '/assets/logo.svg') {
asset.src = customizations.logo
}
})
}Phase 1: Simple Export/Import
- Export button in Silex dashboard
- Downloads .zip with website.json + collections
- Import uploads .zip, creates new site
- No customization UI yet
Phase 2: Template Gallery
- Built-in templates in Silex
- "New from Template" button
- Gallery UI with previews
- One-click creation
Phase 3: Customization
- Template metadata defines customizable fields
- UI for color picker, text inputs, image upload
- Live preview of customizations
- Apply before creating site
Phase 4: Marketplace
- Community templates
- Rating/reviews
- Search/filter
- Premium templates
Storage:
- Templates in filesystem:
templates/library/ - Or in database with version control
- Or remote registry (npm-style)
Versioning:
- Template version in metadata
- Migration scripts for breaking changes
- Compatibility checks
Dependencies:
- TinaCMS schema changes
- Required plugins
- Node version requirements
Security:
- Validate uploaded templates
- Sanitize user inputs
- Prevent code injection in Vue.js snippets
The Silex Dashboard demonstrates a triple-layer architecture:
-
Design Layer (Silex + GrapesJS):
- Visual editing
- Component structure
- CSS styling
- Page settings
-
Content Layer (TinaCMS + 11ty):
- Static content management
- Multi-language support
- GraphQL API
- Static site generation
-
Application Layer (Vue.js):
- Runtime interactivity
- API integration
- State management
- User interactions
Key Benefits:
- Designers work visually in Silex
- Content editors use TinaCMS admin
- Developers add features via Vue.js
- End result: Fast, static, SEO-friendly, interactive site
For Template/Fork Features:
- Package: website.json + collections + assets
- Export: Zip download or API
- Import: Upload or template gallery
- Customize: Colors, text, images via UI
| Task | Files to Edit | How |
|---|---|---|
| Change page design | Use Silex Editor | Visual drag & drop |
| Update UI labels | collections/home/*.md |
Edit in TinaCMS or text editor |
| Add navigation link | collections/settings/*.json |
Edit nav array |
| Add new page | Silex Editor | Pages panel → "+" button |
| Modify Vue.js logic | Silex page settings (Code tab) | Edit <script> in HEAD |
| Add TinaCMS field | tina/config.ts |
Add to fields array |
| Change permalink | Silex page settings (CMS tab) | Edit "Permalink" expression |
| Customize styles | Silex Style Manager | Visual or CSS |
| Content | Path |
|---|---|
| Page structure | silex/websites/dashboard/website.json |
| UI labels | collections/home/*.md |
| Navigation | collections/settings/*.json |
| TinaCMS schema | tina/config.ts |
| 11ty config | 11ty/eleventy.config.mjs |
| Templates | templates/*.html |
| Final output | _site/ |
| Endpoint | Purpose |
|---|---|
http://localhost:6805/?id=dashboard |
Silex editor |
http://localhost:4001/admin |
TinaCMS admin |
http://localhost:4001/graphql |
GraphQL API |
http://localhost:8080/en/ |
Preview (English) |
http://localhost:8080/fr/ |
Preview (French) |
Last Updated: December 27, 2025 Maintainer: Silex Team Questions?: https://docs.silex.me or https://community.silex.me