Docs Getting Started

Introduction

Klar is a visual CMS for static websites. Edit text, images, links, and icons directly on your live webpage, then save the changes back to GitHub.

Click-to-Edit
Hover to highlight, click to edit any text, image, SVG, or link element directly on your page.
Clean HTML Output
Your edits are applied cleanly. No injected wrappers, no data attributes — your HTML stays exactly as you wrote it.
GitHub-Native
Commit edits directly to your repo, publish to GitHub Pages, and manage media files — all from the browser.
AI-Assisted
Ask AI to restyle a section, rewrite copy, or wire up a dynamic list — review the diff, then apply.
Dynamic Content
Model pages as structured content and render blog indexes, archives, and card grids from live data with listing templates.
Backend-Free Apps
Let visitors sign in and power bookings, comments, and RSVPs with shared collections, an admin view, and email/SMS automations — no server to run.

What You Can Do#

  • Click-to-edit — hover to highlight, click to edit any text, heading, button, or link
  • Structured panel — edit text, attributes (href, src, alt, class, style), and raw HTML in a sidebar form
  • Image and media management — upload, browse, and swap images from your GitHub-backed media library
  • Icon picker — search and insert icons from Heroicons, Material Design, and Lucide libraries
  • SVG editing — edit SVG attributes like stroke, fill, and viewBox, or replace SVGs entirely
  • Link editing — edit href on any anchor element, even when the link wraps images or other content
  • List/container editing — add, remove, reorder, and duplicate items in repeating containers like lists and grids
  • Block editor — split paragraphs, reorder headings and list items, save reusable templates, and duplicate blocks with a floating toolbar
  • Rich text and tables — a rich text inline editor for paragraphs and content blocks, with full table cell, row, and column editing
  • Code editor — edit HTML, CSS, and JavaScript directly with syntax highlighting and live preview
  • AI assistant — ask AI to make changes to your page with natural language
  • Undo and redo — step backwards and forwards through your pending edits with Cmd+Z / Cmd+Shift+Z
  • Multi-page management — create, duplicate, rename, and delete pages in your repo
  • Shared sections — edit a header or footer once and sync it across all pages
  • Save and publish — commit your edits to GitHub and publish to GitHub Pages with one click
  • Version history — browse previous versions of your page and restore any commit
  • Members and invites — invite collaborators to a project as admin or editor via email
  • Form submissions — accept contact-form posts from your live site straight into a Klar inbox
  • Visitor accounts and apps — let visitors sign in and build interactive apps (bookings, comments, RSVPs) with the Users SDK: per-user data, shared collections with server-enforced rules, an owner admin view, and email/SMS automations — no backend to run
  • Responsive preview — test your page at desktop, tablet, and phone sizes, or freely resize the viewport
  • Light and dark themes — toggle the editor chrome to match your preference

How It Works#

Go to klar.website, sign in with Google, and connect your GitHub repository. Klar loads your page in a live preview. Click on any element to start editing. When you're done, hit Save — your changes are committed directly to your repo as clean HTML.

HTML
<!-- Your original HTML -->
  <section class="hero">
    <h1>Welcome to My Site</h1>
    <p>This is the description.</p>
  </section>
  
  <!-- After editing with Klar — still clean! -->
  <section class="hero">
    <h1>Welcome to My Updated Site</h1>
    <p>A better description here.</p>
  </section>
Ready to get started? Head over to the Setup & Login guide to connect your GitHub repository.
Next Setup & Login →
Docs Getting Started Setup & Login

Setup & Login

Sign in to Klar, connect your GitHub repository, and create your first project.

Sign In#

Go to klar.website and sign in with your Google account. This gives you access to the project dashboard where you can create and manage multiple websites.

What You Need#

  • A GitHub repository with your static website HTML files
  • A GitHub personal access token with repo and pages scope (needed to save and publish changes)
To create a GitHub token, go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic), and generate a new token with repo and pages scope. Your token is encrypted before being stored.

Create a Project#

From the dashboard, click New Project. Fill in the following:

Field Description
Website URL The live URL of your site (used to load your page and resolve relative paths)
GitHub Repository In owner/repo format, e.g. myname/mysite
GitHub Token Your personal access token for reading and writing files
HTML File Path to the HTML file in your repo, e.g. index.html

You can also optionally set an Images Folder (for the media library), CSS File and JS File paths (for the code editor), and more. See Project Settings for all options.

The Dashboard#

Your dashboard shows all your projects. You can search projects by repository name or website URL, sort by recent or alphabetically, and click any project to open it in the editor. Each project card shows the repository, website URL, and last updated time. Use the gear icon to edit settings, or the delete button to remove a project.

Previous ← Introduction Next Quick Start →
Docs Getting Started Quick Start

Quick Start

Go from sign-in to your first edit in under a minute.

1. Sign In & Create a Project#

Go to klar.website, sign in with Google, and create a new project with your GitHub repository details (repo, token, and HTML file path).

2. Start Editing#

Klar loads your page in a live preview. Hover over any element to see it highlighted with a blue outline. Click to select it — text elements become editable right on the page, while images and SVGs open the structured panel on the right.

Use the sections panel on the left to navigate between page sections like header, content areas, and footer. Click a section name to scroll directly to it.

3. Make Changes#

Edit text by typing directly in the preview. Use the structured panel to change attributes like links, image sources, alt text, CSS classes, and styles. You can also open the code editor for direct HTML, CSS, and JavaScript editing, or use the AI assistant to make changes with natural language.

The toolbar shows a badge with your pending edit count. Each change is tracked and can be individually reverted from the structured panel.

4. Save & Publish#

Press Cmd+Shift+S (or click Save) to commit your changes to GitHub. Your HTML stays clean — no wrappers or data attributes are injected.

Press Cmd+Shift+P (or click Publish) to also deploy your site to GitHub Pages. Klar creates a dedicated publish branch and enables Pages automatically.

That's it! You're now editing your website visually and saving changes directly to GitHub. Explore the Visual Editing guide to learn about all the editing capabilities.
Previous ← Setup & Login Next Visual Editing →
DocsEditingVisual Editing

Visual Editing

Click directly on your page to edit text, images, links, icons, and more.

How It Works#

Your website loads in a live preview inside the editor. Hover over any element to see it highlighted with a blue outline. Click to select it — the element becomes editable directly on the page.

Editable Elements#

By default, Klar makes the following element types editable:

  • Text elements — headings (h1–h6), paragraphs, spans, links, buttons, list items, table cells, labels, blockquotes, and more
  • Images — click to change the source, alt text, or pick a new image from the media library
  • SVGs & Icons — edit SVG attributes (stroke, fill, viewBox, width, height) or replace the icon entirely using the icon picker
  • Links — edit the href on any anchor element, even when the link wraps images or other content
  • Background images — swap background images on elements with the :style(background-image) selector

Text Editing#

Click any text element to start typing directly on the page. Your changes are tracked and shown in the structured panel on the right. Line breaks are preserved using <br> tags. Press Enter to confirm and deselect, or Shift+Enter to insert a line break inside the field.

Paragraphs and content blocks open a rich text editor for bold, italic, headings, lists, and links. Pasted content is sanitized so styling from the source is stripped out and the result stays clean.

Block Toolbar#

When you hover a block inside a list, grid, or content group, a small floating toolbar appears next to it. From the toolbar you can:

  • Add — open a menu of block types and your saved templates, then insert the one you pick
  • Duplicate — copy the current block, including any connected partner element
  • Move up / Move down — reorder blocks inside their container
  • Save as template — store the block's HTML as a reusable template for this project
  • Delete — remove the block from the page

The toolbar follows the hovered block and adapts its position for both vertical lists and horizontal rows.

Table Editing#

Click inside any table cell to edit its contents. A cell toolbar lets you add or remove rows and columns, while text inside each cell is edited with the same rich text controls as paragraphs.

Undo & Redo#

Press Cmd+Z to step backwards through your pending edits and Cmd+Shift+Z (or Cmd+Y) to step forwards again. Undo also covers structural changes like add, duplicate, reorder, and delete inside the block toolbar — the iframe is kept in sync so the preview always matches the underlying edit state.

Container & List Editing#

For repeating elements like lists, feature grids, or card layouts, Klar provides a dedicated container editor. Hover over the container and click the "Edit list" button that appears. This opens a modal where you can:

  • Edit text fields within each item
  • Add new items
  • Remove items
  • Reorder items
  • Duplicate items

Containers are detected automatically, or you can mark them with the [] selector suffix or the data-klar-list attribute. See Custom Selectors.

Sections Navigation#

The left panel shows your page structure organized by sections (header, content areas, footer, etc.). Click any section name to scroll the preview directly to it. Sections are auto-detected from your HTML structure, or you can configure a custom sections selector in Project Settings.

Script Toggle#

By default, JavaScript in your page is disabled in the editor to prevent interference with editing. You can enable it from the Editor tab in project settings to test dynamic content like animations or interactive elements.

Previous ← Quick Start Next Structured Panel →
DocsEditingStructured Panel

Structured Panel

The sidebar form for precise editing of text, attributes, and HTML.

Overview#

The structured panel is the right sidebar. When you select an element in the visual editor, the panel shows editable fields for that element. You can toggle the panel on or off from the toolbar.

What You Can Edit#

Depending on the element type, the panel shows different fields:

Field Available On Description
Text All text elements Edit the text content with live preview
Href Links (<a>) Edit the link URL or anchor reference
Src Images Change image source via URL or media picker
Alt Images Edit alt text for accessibility
Classes All elements Edit CSS class names
Style All elements Edit inline CSS styles
Background Image Elements with background-image Swap background images via media picker
SVG attributes SVG elements Edit stroke, fill, viewBox, width, height, stroke-width, stroke-linecap
Aria-label Headings (h1–h6) Edit accessibility labels

Element List#

When no element is selected, the panel shows a scrollable list of all editable elements on the page. Each element card shows:

  • A color-coded tag badge (green for headings, blue for links, etc.)
  • The CSS selector (truncated)
  • A text preview
  • Edit status indicators for changed fields

Click the locate icon on any element to highlight and scroll to it in the preview. Click the revert button to undo changes for that element.

Edit Tracking#

Edited fields are highlighted with a yellow border, and the original value is shown below for comparison. Each field has an individual revert button to undo that specific change. The toolbar shows a badge with the total number of pending edits.

Raw HTML Editing#

Every selected element exposes a raw HTML view powered by CodeMirror. Open it to edit the element's outerHTML directly, with HTML syntax highlighting. Useful for replacing an SVG icon with custom markup or for surgical fixes that don't fit the structured fields.

Pick Mode#

Some settings (like the sections selector) accept a CSS selector. Click the Pick button next to the field and then click any element on the page — Klar copies the element's selector into the field for you, so you don't have to write it by hand.

Blocks Tab#

When the selected element is a section or container, the blocks tab lists every child block as a draggable row. Expand a row to edit its text fields, drag the handle to reorder, duplicate or delete from the inline action icons, or save the block as a reusable template for this project.

Editing a link href shows a small bubble above the input with a Pick link button to choose another page or anchor in your repo, plus a Choose file button to point the link at a file from your media library. The picker lists the HTML files in your project and any in-page id targets; picked pages are inserted as root-relative paths (e.g. /blog/post.html) so internal links keep working after you rename or move pages.

Previous ← Visual Editing Next Code Editor →
DocsEditingCode Editor

Code Editor

Edit HTML, CSS, and JavaScript directly with syntax highlighting and live preview.

Opening the Code Editor#

Toggle the code editor from the toolbar. The editor panel can be positioned on the left, right, or bottom of the screen, and you can resize it by dragging the edge.

HTML, CSS & JS Tabs#

The code editor has separate tabs depending on your project configuration:

  • HTML — always available. Edit the full HTML source of your page.
  • CSS — available when you've configured a CSS file path in project settings. Changes are previewed live in the visual editor.
  • JS — available when you've configured a JS file path in project settings. Changes are previewed live.

CSS and JS changes are saved alongside HTML when you click Save or Publish.

Features#

  • Syntax highlighting for HTML, CSS, and JavaScript
  • Line numbers
  • Code folding (fold state is saved per project)
  • Click a section in the left panel to jump to the relevant HTML
  • Apply button to load your modified HTML into the visual preview
Previous ← Structured Panel Next AI Assistant →
DocsEditingAI Assistant

AI Assistant

Use natural language to make changes to your page with AI-powered editing.

Overview#

The AI chat panel lets you describe changes in plain English and have them applied to your page. Toggle it from the toolbar; like the code editor it can dock on the left, right, or bottom. You bring your own AI provider key (added in settings), so you pick the model and stay in control of cost.

Modes#

Choose a mode under the message box — it tells the AI what kind of change you want:

Mode What it does
Page Generate a complete new page from a prompt.
Modify page Change the current page; returns the full updated document so you can review a diff before applying.
Section Generate a self-contained block or section to insert into the page.
Edit Change the element you selected in the editor.
Chat Ask questions about your site without generating code.
Site / Site Strict Generate a complete multi-page website from a brief (Strict also builds a reusable design system).

Editing a selected element#

In Edit mode, click any element in the visual editor and the AI gets its context — ask "make this heading larger" or "change this button to blue" and it knows which element you mean. This works on normal HTML and on elements rendered at runtime by JavaScript or React: for those there's no static HTML to swap, so the AI finds the code that generates the element and edits that instead.

Reviewing & applying changes#

Nothing touches your page until you apply it. When the AI produces code you'll see apply buttons — and for whole-page changes (Modify page, and Edit on a runtime element) you can open View diff to see a before/after of the entire page first.

  • Apply / Replace Element — apply the change
  • View diff — review the full before/after first
  • Apply CSS / JS — apply style or script changes

What the AI can see#

Chips under the message box control the context sent with your prompt — the page HTML, your project CSS and JS, other Site files, and your design system. Modes that need the page (Modify page, Edit) attach it automatically; the chips are a manual override for the other modes. A lit chip means that context is being included.

Design system mode#

Turn on DS to have the AI reuse your project's existing classes and tokens instead of inventing new styles. In Strict it only reuses what already exists; otherwise it can extend your style.css when a needed style is genuinely missing — keeping generated markup consistent with the rest of your site.

Previous ← Code Editor Next Media & Icons →
DocsFeaturesMedia & Icons

Media & Icons

Upload, browse, and swap images from your GitHub repo, and search icons from multiple libraries.

Media Library#

When you click an image element, the structured panel shows the current source and alt text, plus a button to open the media library. The media library has three tabs:

  • Images — browse existing images from your configured media folder in GitHub
  • Upload — drag and drop or click to upload new images directly to your repo
  • URL — enter a direct image URL

You can also delete unused images and copy file URLs. File sizes are displayed for each image.

Assets Folder#

If you configure an assets folder in project settings, you also get a file browser for non-image files (fonts, documents, etc.) with upload and delete capabilities.

Icon Picker#

For SVG icons, click the icon picker button when an SVG element is selected. The icon picker includes:

  • Heroicons, Material Design Icons, and Lucide libraries
  • Real-time search across all icon sets
  • Visual grid preview
  • Click any icon to insert it as inline SVG, replacing the current icon

The picker remembers your last selected library and search query.

Background Images#

Elements with CSS background images can be edited through the media library too. Use the :style(background-image) selector modifier or the data-klar-style-bg HTML attribute to enable this on specific elements.

Previous ← AI Assistant Next Page Management →
DocsFeaturesPage Management

Page Management

Create, duplicate, rename, and delete pages in your repository.

Page Switcher#

The page switcher dropdown in the toolbar shows the current file path and lets you switch between all HTML files in your repository. Click any page to load it in the editor.

Page Actions#

  • Create page — add a new HTML file to your repository
  • Duplicate page — copy an existing page with a new filename
  • Rename page — change a page's filename
  • Delete page — remove a page from your repo (requires confirmation)

All page operations are committed directly to your GitHub repository.

Page Types#

A page type is a reusable template for pages that share a structure and the same content fields — blog posts, products, team members. A page type is itself an editable page (for example page-types/blog-post.html); when you create or duplicate a page from it, the layout and field definitions come along, and each new page stores its own field values. Those values feed listing templates and the content API.

Per-Page Settings#

Editor settings can be overridden for a single page without touching the rest of the site — useful when one page needs a different editable scope or shared-section behavior. See Per-Page Overrides.

Open Website#

Use the "Open website" link in the toolbar to open the live version of your site in a new tab, using your configured website URL and current file path.

Previous ← Media & Icons Next Shared Sections →
DocsFeaturesShared Sections

Shared Sections

Edit a header or footer once and sync it across all pages in your site.

What Are Shared Sections?#

Most websites have elements that appear on every page — a navigation header, a footer, a cookie banner. Shared sections let you edit these once and push the changes to all pages in your repository.

Setting Up Shared Sections#

In your project settings (Editor tab), enter CSS selectors for the sections that are shared across pages. For example:

  • nav, footer — sync the navigation and footer
  • .shared-header, .shared-footer — sync elements with specific classes

How Syncing Works#

There are two ways to sync shared sections:

  • Manual sync — if Show "Sync to all pages" in left panel is enabled, a sync icon appears next to shared sections in the sections panel. Click it to sync that section across all pages.
  • Auto-sync on save — if Sync Shared Sections on Save is enabled, any edits to shared sections are automatically pushed to all other pages when you save.

After syncing, you'll see a toast confirmation showing how many pages were updated (e.g., "Synced to 3 pages").

Previous ← Page Management Next Save & Publish →
DocsFeaturesSave & Publish

Save & Publish

Commit your edits to GitHub and deploy to GitHub Pages.

Saving#

Click Save in the toolbar (or press Cmd+Shift+S) to commit your changes to GitHub. Klar applies your edits to the original HTML file in your repo and creates a new commit. Your HTML output is always clean — no editor artifacts, wrappers, or data attributes are added.

If you have CSS or JS files configured, any changes made in the code editor are saved alongside the HTML in the same operation.

Publishing#

Click Publish (or press Cmd+Shift+P) to save your changes and deploy to GitHub Pages. Publishing does everything Save does, plus:

  1. Creates or updates a dedicated klar-publish branch from the latest commit
  2. Enables GitHub Pages on that branch
  3. Returns the live Pages URL so you can see your site immediately
There is also a "Publish latest" option that deploys the current state of your main branch to GitHub Pages without saving new edits. Useful when you've committed changes through other means and just want to deploy.

Pages that contain listing templates are re-rendered at publish, so blog indexes and card grids always reflect the latest content you've saved.

Shared Section Sync#

If you have shared sections configured and auto-sync enabled, any edits to those sections are automatically pushed to all other pages when you save. The success toast shows how many pages were synced.

Previous ← Shared Sections Next Version History →
DocsFeaturesVersion History

Version History

Browse previous versions of your page and restore any past commit.

Viewing History#

Open the version history tab in the structured panel to see a list of commits for the current HTML file. Each entry shows:

  • Commit message
  • Author name
  • Date and time
  • Commit SHA

The list is paginated — use the First / Prev / Next / Last controls to page through history (Next and Last load older commits).

Restoring a Version#

Click any commit to load that version of the page into the editor. You can review the content, then either save it as the new current version or click Back to current version to return to the latest version.

Previous ← Save & Publish Next Members & Invites →
DocsFeaturesMembers & Invites

Members & Invites

Invite collaborators to a project and control what they can change.

Roles#

Each member of a project has one of two roles:

  • Admin — can edit content, manage members, rotate the form token, and change project settings. The project owner is always an admin and can't be removed.
  • Editor — can edit and save content but can't manage members or change project-level settings.

Inviting a Member#

Open project settings and switch to the Members tab. Type the invitee's email address, choose a role, and click Invite. Klar generates a tokenized invite URL for that email address only.

If SMTP is configured, the invite link is sent automatically from the Klar mail address. If SMTP isn't configured, the invite URL is shown in the panel so you can copy and share it manually.

Accepting an Invite#

The recipient opens the invite URL and signs in with the Google account that matches the invited email address. After accepting, the project shows up in their dashboard.

Managing Members#

Admins can remove a member from the Members tab. Two safety checks apply automatically:

  • You can't remove yourself.
  • You can't remove the last remaining admin — promote someone else first if you want to leave.
Previous ← Version History Next Form Submissions →
DocsFeaturesForm Submissions

Form Submissions

Accept contact-form posts from your live site straight into a Klar inbox.

Overview#

Every project gets a unique, tokenized submission URL of the form POST /api/forms/<form-token>/submit. Point an HTML <form> on your customer site at that URL and the submitted fields land in your Klar inbox.

Connecting a Form#

The Forms tab in project settings shows the submission URL and a ready-made HTML snippet you can paste into your site:

HTML
<form action="https://klar.website/api/forms/YOUR_TOKEN/submit" method="POST">
  <input name="name" placeholder="Your name" required>
  <input type="email" name="email" placeholder="Your email" required>
  <textarea name="message" placeholder="Message" required></textarea>
  <!-- Honeypot: hidden from users, bots fill it in and get silently dropped. -->
  <input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
  <button type="submit">Send</button>
</form>

Anti-Abuse Defenses#

  • Origin allowlist — submissions are only accepted from the project's websiteUrl, the matching GitHub Pages host, and any extra origins you list in Additional Origins.
  • Honeypot fields — any input whose name starts with an underscore (for example _gotcha) is treated as a honeypot. If a bot fills it in, the submission is silently dropped.
  • Rate limit — capped at 20 submissions per hour per IP and per project.
  • Body size cap — request bodies larger than 256 KB are rejected.

Inbox#

Submissions are stored in Klar and listed in the Forms tab. Click a row to expand it, mark it as read, or delete it. Unread submissions are highlighted. If SMTP is configured, the project owner also receives an email for each submission with the sender's email set as Reply-To.

Rotating the Token#

If the submission URL leaks or starts receiving abuse you can't filter, click Regenerate in the Forms tab. The old URL stops accepting submissions immediately — update the form on your site with the new URL afterwards.

Previous ← Members & Invites Next Project Settings →
DocsConfigurationProject Settings

Project Settings

Configure your project with repository details, editing scope, media paths, and more.

Opening Settings#

Click the gear icon in the toolbar to open the settings modal. Settings are organized into tabs: General, GitHub, Editor, Members, Forms, Collections, SMS, Tags, Publishing, and Danger Zone. Tabs that depend on a saved project (Members, Forms, Collections, SMS, Tags) are disabled until you create the project for the first time.

General Tab#

Page metadata that's written into the <head> of your saved HTML:

Setting Description
Title The browser-tab and search-result title (<title>)
Meta description The search-result snippet (meta name="description") — aim for about 150–160 characters
Favicon URL or path to the icon shown in browser tabs (link rel="icon")

Use the Project / Page toggle to set a default for the whole site or override the metadata just for the page you have open.

GitHub Tab#

Setting Description
Website URL The live URL of your site (used to load the page and resolve relative paths like images and CSS)
GitHub Token Your personal access token for reading and writing files. Encrypted before storage.
GitHub Repository Your repo in owner/repo format
HTML File Path to the HTML file in your repo (e.g., index.html or pages/about.html)
CSS File Optional path to a CSS file for live editing in the code editor
JS File Optional path to a JS file for live editing in the code editor
Images Folder Path to the folder where media library images are stored (e.g., images)
Assets Folder Optional path for non-image files (fonts, documents, etc.)

Editor Tab#

Setting Description
Editable Elements CSS selectors that define which areas of your page are editable. See Custom Selectors.
Sections Selector CSS selector that defines how the left panel discovers page sections. Leave empty for auto-detection.
Shared Sections Selectors for sections that appear on all pages (e.g., nav, footer). See Shared Sections.
Sync Shared Sections on Save Toggle to automatically sync shared sections to all pages when saving
Show "Sync to all pages" in left panel Toggle to show sync icons on shared sections in the sections panel
JavaScript Toggle to enable or disable JavaScript execution in the editor preview

Members Tab#

Invite collaborators to a project by email. Each member is either an admin (can manage settings and other members) or an editor (can edit and save content). See Members & Invites.

Forms Tab#

Each project gets a tokenized form-submission URL you can point HTML <form> elements at. Manage allowed origins, regenerate the token, and read inbound submissions here. See Form Submissions.

Collections Tab#

Define the shared data collections your site's apps use — bookings, comments, RSVPs — and the integrity rules Klar enforces on every write: unique, no-overlap (makes double-booking impossible), and capacity. Apps read and write these through the Users SDK (user.collection()), and because the rules live on the server a client can't get around them. (Available once the project has been saved.)

SMS Tab#

Connect your own 46elks account — a developer-friendly SMS API that sends worldwide — so your site's automations can text people (booking confirmations, reminders). Paste the API username + password and pick a sender name (your brand); the credentials are stored encrypted and you pay 46elks directly. (Available once the project has been saved.)

Tags Tab#

Define the vocabularies — reusable lists of allowed values like categories, tags, or authors — that back your tags and select content fields. Editors then pick from these lists instead of free-typing, which keeps values consistent and powers faceted filtering in listings. (Available once the project has been saved.)

Publishing Tab#

Setting Description
Pages Branch The branch Klar copies your published files onto when you click Publish. Defaults to klar-publish.
Production Branch The branch Klar treats as your site's production source. Leave blank to use the repository's default branch.

Per-Page Overrides#

Editor settings (Editable Elements, Sections Selector, Shared Sections, Sync Shared Sections on Save, Show "Sync to all pages" in left panel, and JavaScript) inherit in this order: Klar default → project value → page override. When you have a specific page open, each setting shows a "Set for this page" control so you can override the project default for that page only without touching the others.

Danger Zone#

The danger zone tab lets you delete the project entirely. This removes the project from your Klar account but does not affect your GitHub repository.

Previous ← Form Submissions Next Custom Selectors →
DocsConfigurationCustom Selectors

Custom Selectors

Control which elements are editable using scope selectors, container syntax, and data attributes.

Editable Selectors#

By default, Klar makes all standard text elements editable across the entire page. The Editable Elements setting in the Editor tab lets you restrict which areas are editable by providing CSS selectors.

Examples
# Only elements inside .hero and .content are editable
.hero, .content

# Everything in .hero except links and images
.hero:not(a, img), .content

# Mark a container for list editing
.feature-list[]

# Enable background-image editing
.hero__bg:style(background-image)

Selector Syntax#

Syntax Meaning
.hero All editable elements inside .hero
.hero:not(a, img) Everything in .hero except links and images
.feature-list[] Treat .feature-list as a container for list editing
.bg:style(background-image) Enable background-image editing on .bg

HTML Data Attributes#

You can also mark elements directly in your HTML without configuring selectors:

  • data-klar-edit or data-klar — mark any element as editable regardless of tag name
  • data-klar-list or data-list — mark a parent as a list container for container editing
  • data-klar-style-bg — enable background-image editing on that element

Content Fields#

Editable selectors do more than mark text editable — adding a :field(...) rule turns a matched element into named, structured content (a title, image, tag list, date, and more) that's saved per page. This is how you model blog posts, products, and other page types. See Content Fields for the full syntax, types, and options.

Quick reference#

Modifier Effect
:not(…) Exclude matching elements from the scope
[] Treat the element as a list/container for container editing
:blocks Make the element a drag-and-drop block container — its children get the block toolbar (add, duplicate, reorder, delete)
:readonly Mark matching elements read-only so they're never editable, even inside an editable scope
:snapshot Capture the element's content at save time; read-only inline (implies :readonly)
:style(background-image) Make the element's background-image swappable from the media library (background images only)
:field(…) Capture the element as a named content field
Previous ← Project Settings Next Content Fields →
DocsContentContent Fields

Content Fields

Turn parts of a page into named, structured data — titles, images, tags, dates, rich blocks — saved per page and reused across your site.

Overview#

A field is a named piece of content attached to a page. You declare fields by adding :field(...) rules to your Editable Elements (Editor tab). Each field's value is saved with the page, rendered back into the page on save, and made available to listing templates and the content API. Fields are what turn a page into a "blog post", a "product", or a "team member" rather than just markup.

Declaring a field#

Write a CSS selector, then :field(...) with at least a name (the storage key) and a type:

Editable Elements
# bind a heading's text to a "title" field
h1.post-title :field(title: Title, name: title, type: string)

# bind an image's src to an "image" field
img.hero :field(name: image, type: image)

# a tag picker backed by a project vocabulary
.tags :field(name: tags, type: tags, options(vocab: tags))

The selector decides which element shows the value; the name is how the value is stored and referenced everywhere else.

Field types#

Type Use for
string Short single-line text (headings, labels)
text Longer multi-line text (excerpts, descriptions)
number / integer Numeric values
boolean On/off toggle
image An image path, picked from the media library
link A URL with an optional label
tags / select Multiple (tags) or single (select) choice, optionally from a vocabulary
array Repeatable blocks or list items (rich content)
slug / location URL slug / geographic point

A string can be narrowed with a format — e.g. date, email, url, markdown, or html.

Options#

Extra settings tune how a field looks and behaves. Top-level keys control layout; an options(...) group holds the rest:

Option Meaning
title Label shown in the editor
order Position of the field in the panel
col Width in the form grid (e.g. col: 6 = half)
section / sectionOrder Group fields under a labelled section
default Starting value for new pages
options(vocab: …) Back a tags/select field with a project vocabulary
options(layout: …) Editor layout — e.g. blocks, list, editor, chip, radio
options(dateFormat: …) Display format for a date field

Tags & vocabularies#

A vocabulary is a reusable list of allowed values (categories, tags, authors) configured in Project Settings → Tags. Bind a field to one with options(vocab: name) so editors pick from the list instead of free-typing. Use type: tags for multiple values and type: select for one — these power faceted filtering in listings.

Data-only fields#

A field doesn't need an element on the page. Omit the selector to store a value with no on-page target — handy for an excerpt, SEO description, or other metadata:

Editable Elements
:field(name: lead, type: text, default: 'Short summary shown in listings.')

Editing fields#

Element-bound fields can be edited inline on the page, and the right panel's Edit all fields view lists every field for the page in one form — including data-only fields that have no place on the page. Values are saved per page when you Save.

Previous ← Custom Selectors Next Listing Templates →
DocsContentListing Templates

Listing Templates

Render dynamic lists — blog indexes, card grids, archives — from your page content, with no manual copy-paste.

Overview#

A <template data-for data-query> block renders a list of pages into a container on your page. Every time you save, Klar runs the query and replaces the container's children with the result. The <template> itself stays in your HTML as the source of truth, so you can keep editing the design.

Example#

HTML
<div id="post-list"></div>

<template data-for="post-list"
  data-query="list('blog-post', { published: true, order: 'published_at:desc', limit: 10 })">
  {% for item in items %}
    <a href="{{ item.url }}">
      <img src="{{ item.content.image.src }}" alt="{{ item.content.image.alt }}">
      <h3>{{ item.content.title }}</h3>
    </a>
  {% endfor %}
</template>

The body is a Liquid template with the matched pages available as items. You get the full loop toolkit — {% for %}, forloop.index/first/last, {% cycle %}, and nested loops.

Query verbs#

Verb Returns
list('type', { … }) Pages of a page-type — supports order, limit, offset, published, where
facets('type', 'field') Distinct values and counts for a field (for filter menus)
vocabularies() Every project-configured vocabulary (the full tag map) — takes no argument

Filtering#

Narrow a list by field values with where — for example only posts tagged css in the fashion category:

Query
list('blog-post', { where: { category: 'fashion', tags: 'css' } })

When listings update#

Listings re-render every time you save the page they live on, and Klar refreshes all listing pages at publish so they always show your latest content.

Previous ← Content Fields Next Content API & SDK →
DocsDevelopersContent API & SDK

Content API & SDK

Read your published content and add visitor sign-in with per-user data on any site, using small drop-in JavaScript modules.

Overview#

Two ES modules are served straight from your Klar host — no install, no build step. The Content SDK reads the content you manage in Klar; the Users SDK lets visitors of your site sign in and store their own data. Both are scoped by your projectId (found in Project Settings).

Reading content#

JavaScript
// served from your Klar host — no build step
import { createKlarClient } from 'https://<your-klar-host>/sdk/v1/content.js';

const klar = createKlarClient({ projectId: 42 });
const posts = await klar.list('blog-post', { published: true, order: 'published_at:desc' });

document.querySelector('#list').innerHTML =
  klar.render(posts, `<a href="${url}"><h3>${content.title}</h3></a>`);

list(), facets() and vocabularies() mirror the listing query verbs, and render() fills a template with each item. A content-static.js variant reads a pre-built JSON file for fully static sites.

Sign-in & per-user data#

Let visitors sign in with Google — through a Klar-hosted popup, so there's no Google setup on your side — and read or write their own data:

JavaScript
import { createKlarUser } from 'https://<your-klar-host>/sdk/v1/users.js';

const user = createKlarUser({ projectId: 42 });
await user.signIn();

await user.data.set('favorites', ['post-1', 'post-7']);
const favs = await user.data.get('favorites');

Each visitor only ever reads and writes their own data, scoped to your project. Sign-in identifies visitors and gives them personal storage — it does not lock down the page source. The Users SDK also does a lot more — shared collections for app data (bookings, comments), email & SMS automations, and an admin namespace for the project owner — see the Users SDK reference.

Full reference#

The complete API — every option, return shape, and the static variant — has its own reference page: Content SDK and Users SDK.

Previous ← Listing Templates Next Content SDK →
DocsDevelopersContent SDK

Content SDK

Read your published content from any site — list pages, filter by field, resolve asset paths. Full reference for createKlarClient (/sdk/v1/content.js).

Quick start#

JavaScript
import { createKlarClient } from 'https://<your-klar-host>/sdk/v1/content.js';

const klar = createKlarClient({ projectId: 42 });

const posts   = await klar.list('blog-post', { published: true, order: 'published_at:desc', limit: 10 });
const fashion = await klar.list('blog-post', { where: { category: 'fashion', tags: 'css' } });
const tags    = await klar.facets('blog-post', 'tags');
const vocab   = await klar.vocabularies('blog-post');

Served from your Klar host — no install, no build step. Everything is scoped by your projectId (Project Settings). These endpoints are public and read-only.

createKlarClient(config)#

Config Required Description
projectId Yes Numeric project ID
baseUrl Klar host; defaults to where the SDK was loaded from
cdn { repo, branch } — enables cdn() rewriting to jsDelivr

Returns a client with list, facets, vocabularies, cdn, render, and insert.

list(pageType, options)#

Option Default Notes
published both true = published only, false = drafts only
order created_at:desc column ∈ published_at / created_at / updated_at / file_path; dir asc|desc
limit 50 1–200
offset 0 skip N
where filter by field values (see below)
signal AbortSignal to cancel the request

Returns ContentItem[]. A where clause matches scalar fields by equality and array (tags) fields by membership; multiple keys are AND'd:

Query
await klar.list('blog-post', { where: { category: 'fashion', tags: 'css' } });
// explicit operators when you need them:
await klar.list('blog-post', { where: { tags: { has: 'css' }, status: { eq: 'live' } } });

facets & vocabularies#

facets(pageType, field, options) returns distinct values and counts for one field (for filter menus), sorted by count. vocabularies(pageType) returns the project's tag vocabularies for that page-type. Both accept published, where, and signal.

ContentItem shape#

Object
{
  filePath:    "blog/post-1.html",   // repo-relative
  url:         "/blog/post-1.html",  // gets /<repo> prefix on GitHub Pages project sites
  pageType:    "blog-post",
  content:     { title: "…", image: { src: "…", alt: "…" }, tags: ["css"] }, // your :field() values
  pathFields:  ["image"],            // names of src/href fields
  createdAt:   "2026-05-01T…",
  publishedAt: "2026-05-02T…",       // null if unpublished
  updatedAt:   "2026-05-03T…"
}

render & insert#

render(data, template) fills ${dotted.path} placeholders from an item (or array), walking into content — values are inserted un-escaped. insert(selector, position, html) places HTML relative to an element; positions are before, after, prepend, append, replace.

JavaScript
const posts = await klar.list('blog-post', { published: true });
klar.insert('#list', 'replace', klar.render(posts, `
  <a href="${url}">
    <img src="${content.image.src}" alt="${content.image.alt}">
    <h3>${content.title}</h3>
  </a>`));

Asset & URL resolution#

Path-field values on content are rewritten automatically by list() per environment: in the Klar editor → jsDelivr; on a GitHub Pages project site → prefixed with /<repo>; on a custom domain → unchanged. Use client.cdn(path) for ad-hoc paths. Absolute URLs and data: URIs always pass through.

One-shot, errors & static#

  • fetchContent({ projectId, pageType, … }) — a one-shot list without keeping a client around.
  • Failed requests throw KlarApiError ({ status, message }).
  • content-static.js — the same API, backed by a pre-built JSON file for fully static sites.

Raw endpoints#

HTTP
GET /api/content?projectId&page_type&published&order&limit&offset&where   → { items }
GET /api/facets?projectId&page_type&field&published&where                 → { items }
GET /api/vocabularies?projectId&page_type                                   → { vocabularies }
Previous ← Content API & SDK Next Users SDK →
DocsDevelopersUsers SDK

Users SDK

Sign visitors in, give each one their own data store, share data across users with collections, send email & SMS automations, and read it all back as the project admin. Full reference for createKlarUser (/sdk/v1/users.js).

Sign-in here is for identification and per-user storage, not access control — the page source stays public. No Google Cloud setup is needed on your side; Klar's own OAuth runs the popup.

Quick start#

HTML
<button id="login">Sign in</button>
<button id="logout">Sign out</button>
<script type="module">
  import { createKlarUser } from 'https://<your-klar-host>/sdk/v1/users.js';
  const user = createKlarUser({ projectId: 123 });

  user.onChange(profile => {
    document.querySelector('#login').hidden  = !!profile;
    document.querySelector('#logout').hidden = !profile;
  });

  document.querySelector('#login').onclick  = () => user.signIn();
  document.querySelector('#logout').onclick = () => user.signOut();
</script>

createKlarUser(opts)#

Option Required Description
projectId Yes Your Klar project id
host Klar host origin (defaults to the SDK's own host)
storageKey Override the localStorage key
Method Description
getUser() Current signed-in user, or null. Synchronous.
signIn() Opens the Google popup; resolves with the user once signed in
signOut() Clears the local session
onChange(fn) Subscribe to sign-in / sign-out / expiry; returns an unsubscribe fn
decodeToken() Decoded JWT claims of the current user, or null

The user object contains sub, projectId, provider, email, name, picture, exp, plus the raw token. Sessions persist in localStorage and auto-expire on the JWT's exp.

Per-user data — user.data#

Key-value storage scoped to (project, signed-in user). All methods require a signed-in user (they throw otherwise).

JavaScript
await user.data.set('favorites', ['post-1', 'post-7']);  // any JSON value
const favs = await user.data.get('favorites');           // undefined if absent
const keys = await user.data.list();                     // [{ key, updatedAt }]
await user.data.delete('favorites');

Constraints: keys 1–200 chars ([A-Za-z0-9._:-]); values anything JSON.stringify handles, ≤ 64 KB.

Sharing a project across apps? user.data is one flat namespace per (project, user), so if a to-do app and a booking app live in the same project they'd collide on a bare settings key. Prefix your keys per app — booking:settings, todo:items — so each app reads only its own. The : is just a convention (any allowed char works); the admin list can then count and filter by that prefix — see Admin access.

Shared collections — user.collection()#

Where user.data is private to each user, a collection is shared across everyone on the project — the backend for bookings, comments, RSVPs, anything multiple people read and write. Each record remembers its owner, so edits and deletes stay owner-scoped.

JavaScript
const bookings = user.collection('bookings');

await bookings.create({ resourceId, start, end, status: 'confirmed' }); // you own this row
const mine = await bookings.list({ where: { resourceId }, order: 'start:asc' });
await bookings.update(id, { status: 'cancelled' });  // only your own
await bookings.remove(id);                           // only your own

Records come back flattened — your fields plus id, ownerId, createdAt, updatedAt. list() takes where, order, limit and offset.

You set each collection's access and integrity rules in Settings → Collections, and Klar enforces them on the server — a client can't get around them:

  • Read access — public, signed-in (the default), or owner-only.
  • Unique — a field or combination must be unique (one RSVP per person, one seat per show).
  • No overlap — no two records share an overlapping time range within a group. A bookings collection grouped by resourceId (only confirmed) makes double-booking impossible.
  • Capacity — at most N records matching a filter (cap tickets per event).

A blocked write rejects with err.status === 409 (err.conflict describes the rule) — catch it and ask the visitor to pick another slot. A client-side check is fine for instant feedback, but the server rule is what keeps bookings correct when people act at the same time.

Admin access — user.admin#

The project owner can read and manage everything their site has collected — and build their own admin dashboard from this SDK. You're recognised as the admin automatically when you sign in with the same Google account that owns the Klar project (no extra setup, no separate login). Everyone else gets a 403.

JavaScript
if (user.isAdmin()) {                       // gate your admin UI
  const { users } = await user.admin.listUsers({ limit: 50 });
  const detail    = await user.admin.getUser(users[0].id);   // { profile, data, records }
  const bookings  = await user.admin.listRecords('bookings'); // all records, any owner
  await user.admin.deleteUser(users[0].id);                  // GDPR delete
}

isAdmin() is a cosmetic check (reads your token) — use it to decide whether to render the admin UI, but it's never the security boundary: every user.admin.* call is re-verified on the server, so a non-admin just gets a 403. getUser(id) returns their user.data values and the records they own; listRecords() returns every record in a collection regardless of owner; and deleteUser() removes the user and their private data while leaving shared records ownerless. Write methods let you edit too: setUserData() / deleteUserData() change a user's stored data, and updateRecord() / deleteRecord() edit or cancel any record.

The only requirement is the shared Google account — your Klar login and your site sign-in must use the same one.

Several apps in one project? listUsers() takes optional scoping so one app's admin shows only its own people and counts — no need to spin up a separate project per app. Have each app write a membership marker to user.data at login, then filter on it:

JavaScript
// at login — tag this user as a member of this app (write once)
await user.data.set('app:booking', { joinedAt: Date.now() });

// in admin — list only this app's users, with app-scoped counts
const { users } = await user.admin.listUsers({
  hasKey: 'app:booking',      // only users who have that marker key
  keyPrefix: 'booking:',      // count only data keys with this prefix
  collection: 'appointments', // count only records in this collection
});

hasKey filters the list to users holding a given user.data key; keyPrefix and collection scope each row's dataKeys / records counts to one app. All three run server-side in a single indexed query, so it stays flat whether you have 30 users or 30,000 — no per-user round-trips. (Why server-side? The list returns only summary counts, not each user's keys/records, so filtering in the client would mean an N-times getUser() fan-out.) Collections are already isolated by name, and getUser() returns one user's full data + records — so scope that detail view in your own UI by key prefix / collection.

Email automations#

Send a templated email on a data event (a booking is created → confirmation) or on a schedule (daily → remind tomorrow's bookers). Like collections, an app declares its automations and self-provisions them.

JavaScript
await user.admin.ensureAutomations({
  confirm: {
    collection: 'bookings', on: 'create', to: 'owner',
    subject: 'Confirmed — {{ data.resourceName }}',
    body: '<p>See you {{ data.start | date: "%b %-d" }}.</p>',
  },
  remind: {
    collection: 'bookings',
    schedule: { every: 'day', atHour: 9 },           // hourly | daily | weekly (UTC)
    window: { dateField: 'start', withinHours: 24 }, // next 24h
    where: { status: 'confirmed' }, to: 'owner',
    subject: 'Reminder', body: '…',
  },
});

Trigger (on): create/update/delete. Schedule: presets (every hour/day/week + atHour), with an optional window to target records by a date field. Recipients are owner (the record's owner) or projectOwner only — never an arbitrary address. subject/body are Liquid; a scheduled reminder fires once per record. Manage with listAutomations(), setAutomationEnabled(), deleteAutomation(). Requires SMTP on the Klar host.

Set channel: 'sms' to send a text instead. SMS has no address from sign-in, so the recipient is a phone field on the record (to: 'field:phone') and the message is a single Liquid text. Klar sends via 46elks (sends worldwide): set your own credentials in Settings → SMS (each project owns its sender id, e.g. Klar). With no credentials it's a no-op.

Raw endpoints#

HTTP
# sign-in flow (the SDK drives these)
GET  /api/sdk/auth/popup?projectId&origin&code
GET  /api/sdk/auth/poll?code        → { token, user }
POST /api/sdk/auth/google           → mints the JWT

# per-user data — header: Authorization: Bearer <token>
GET    /api/sdk/data            → { keys }
GET    /api/sdk/data/:key       → { value }   (404 if absent)
PUT    /api/sdk/data/:key       body { value }
DELETE /api/sdk/data/:key

# shared collections — Bearer token (read can be public via ?projectId)
GET    /api/sdk/collections/:name        → { items }   (?where &order &limit &offset)
GET    /api/sdk/collections/:name/:id    → { item }
POST   /api/sdk/collections/:name        body { data } → { item }   (409 on a rule)
PATCH  /api/sdk/collections/:name/:id    body { data } → { item }   (owner-only)
DELETE /api/sdk/collections/:name/:id    → { deleted } (owner-only)

# admin — project owner only (same Google account as the Klar login); 403 otherwise
GET    /api/sdk/admin/users                    → { users, total }   (?search &limit &offset &hasKey &keyPrefix &collection)
GET    /api/sdk/admin/users/:id                → { profile, data, records }
DELETE /api/sdk/admin/users/:id                → { deleted }
PUT    /api/sdk/admin/users/:id/data/:key      body { value } → { ok }
DELETE /api/sdk/admin/users/:id/data/:key      → { deleted }
GET    /api/sdk/admin/collections/:name        → { items }   (all owners)
PATCH  /api/sdk/admin/collections/:name/:id    body { data } → { item }
DELETE /api/sdk/admin/collections/:name/:id    → { deleted }
GET    /api/sdk/admin/automations              → { automations }
POST   /api/sdk/admin/automations              body { automations } → { name:{created} }
PATCH  /api/sdk/admin/automations/:name        body { enabled } → { ok }
DELETE /api/sdk/admin/automations/:name        → { deleted }
Previous ← Content SDK Next Keyboard Shortcuts →
DocsReferenceKeyboard Shortcuts

Keyboard Shortcuts

All keyboard shortcuts available in the Klar editor.

Global#

Shortcut Action
Cmd+Shift+S Save changes to GitHub
Cmd+Shift+P Publish to GitHub Pages
Cmd+Z Undo the last edit (text or structural)
Cmd+Shift+Z / Cmd+Y Redo the last undone edit
Cmd+B Toggle the left (sections) panel
Cmd+I Toggle the right (structured) panel
Cmd+J Toggle the AI assistant panel
Cmd+Shift+F Toggle both side panels
Cmd+E Toggle zen mode (hide editor chrome)
Cmd+Shift+E Open the code editor
Cmd+Enter Toggle preview mode
Cmd+, Open project settings
Cmd+Shift+, Go to the dashboard
Cmd+1Cmd+9 Jump to page 1–9
Escape Close modals, dialogs, and dropdowns

Visual Editor#

Shortcut Action
Hover Highlight editable element with blue outline
Click Select element for editing
Cmd+Click Deactivate current element and re-discover editable elements
Enter Confirm text edit and deselect
Shift+Enter Insert line break in text field
On Windows/Linux, use Ctrl instead of Cmd for all shortcuts.
Previous ← Users SDK Next Viewport & Preview →
DocsReferenceViewport & Preview

Viewport & Preview

Test your page at different screen sizes and preview changes before saving.

Viewport Sizes#

The toolbar provides preset viewport sizes to test responsive design:

Preset Width
Desktop 1024px
Tablet 768px
Phone 375px
Full width No constraint (responsive)

Responsive Mode#

Toggle responsive mode to freely resize the preview by dragging the left, right, bottom, or corner handles. The current dimensions are shown below the preview (e.g., "375 × 667"). Minimum size is 280 × 200 pixels.

Preview Mode#

Click the eye icon to toggle preview mode. In preview mode:

  • Editing interactions are disabled — you see the page as visitors would
  • Anchor links (#fragment) work with smooth scrolling
  • External links show a "Preview mode — links are disabled" toast
  • Scroll position is preserved when switching between edit and preview modes

Maximize#

Click the maximize button to toggle the preview between windowed and fullscreen mode, hiding the browser chrome simulation.

Previous ← Keyboard Shortcuts