# Directory Structure & Configuration ## Directory Structure ``` my-theme/ ├── templates/ │ ├── assets/ # Static assets (CSS/JS/images etc.) — must be placed here │ │ ├── css/ │ │ │ └── style.css │ │ └── js/ │ │ └── main.js │ ├── index.html # Home page │ ├── post.html # Post detail │ ├── page.html # Single page detail │ ├── archives.html # Post archives │ ├── tags.html # Tag listing │ ├── tag.html # Tag archive │ ├── categories.html # Category listing │ └── category.html # Category archive ├── screenshot.png # Optional Console preview image (Halo 2.25+) ├── theme.yaml # Theme configuration (required) └── settings.yaml # Theme settings form definition (optional) ``` > **Important**: The theme folder name must match the `metadata.name` field in `theme.yaml`; otherwise some assets may fail to load. Halo 2.25+ recognizes the first readable root preview image in this order: `screenshot.png`, `screenshot.jpeg`, `screenshot.jpg`, `screenshot.webp`. The resolved URL is exposed as `Theme.status.screenshot`. ## theme.yaml The theme root directory must contain `theme.yaml`. Minimal runnable config: ```yaml apiVersion: theme.halo.run/v1alpha1 kind: Theme metadata: name: theme-foo # must match the theme folder name spec: displayName: My Theme version: 1.0.0 requires: ">=2.0.0" ``` Full field example: ```yaml apiVersion: theme.halo.run/v1alpha1 kind: Theme metadata: name: theme-foo spec: displayName: My Theme author: name: Author Name website: https://example.com description: An example theme logo: https://example.com/logo.png homepage: https://github.com/example/theme-foo repo: https://github.com/example/theme-foo.git issues: https://github.com/example/theme-foo/issues settingName: "theme-foo-setting" # must match metadata.name in settings.yaml configMapName: "theme-foo-configMap" customTemplates: # optional post: - name: Documentation description: Article in documentation format screenshot: file: post_documentation.html category: - name: Knowledge Base description: Knowledge base category screenshot: file: category_knowledge.html page: - name: About description: About page screenshot: file: page_about.html version: 1.0.0 requires: ">=2.0.0" license: - name: "GPL-3.0" url: "https://github.com/example/theme-foo/blob/main/LICENSE" ``` ### Key Fields | Field | Description | Required | | ---------------------- | ------------------------------------------------------------------- | ---------------------------------------- | | `metadata.name` | Unique theme identifier — **must match the folder name** | Yes | | `spec.displayName` | Display name | Yes | | `spec.version` | Theme version | Yes | | `spec.requires` | Minimum required Halo version | Yes | | `spec.settingName` | Setting resource name — must match `metadata.name` in settings.yaml | No | | `spec.configMapName` | ConfigMap name for persisting settings | No (configure together with settingName) | | `spec.customTemplates` | Custom template configuration | No | > After modifying `theme.yaml`, click "Reload Theme Configuration" on the theme page in Console for changes to take effect. ## settings.yaml Defines a form that is auto-rendered on the theme settings page in Console, using [FormKit](https://github.com/formkit/formkit). ```yaml apiVersion: v1alpha1 kind: Setting metadata: name: theme-foo-setting # must match spec.settingName in theme.yaml spec: forms: - group: style # group name (accessed in templates as theme.config.style.xxx) label: Style formSchema: - $formkit: radio name: color_scheme # field name (accessed as theme.config.style.color_scheme) label: Default color scheme value: system options: - label: Follow system value: system - label: Dark value: dark - label: Light value: light - $formkit: color name: background_color label: Background color value: "#f2f2f2" - group: layout label: Layout formSchema: - $formkit: radio name: nav label: Navigation layout value: "single" options: - label: Single column value: "single" - label: Double column value: "double" ``` ### Using Settings Values in Templates Pattern: `theme.config.[group].[name]` ```html
Double-column nav
``` ### Native FormKit Input Types Commonly used native input components in theme settings (see links for full docs): | `$formkit` value | Purpose | Docs | | ---------------- | --------------------------------- | -------------------------------------------- | | `text` | Single-line text | https://formkit.com/inputs/text.md | | `textarea` | Multi-line text | https://formkit.com/inputs/textarea.md | | `number` | Number input | https://formkit.com/inputs/number.md | | `password` | Password input | https://formkit.com/inputs/password.md | | `radio` | Radio buttons (options list) | https://formkit.com/inputs/radio.md | | `checkbox` | Checkbox (single or multi-select) | https://formkit.com/inputs/checkbox.md | | `color` | Color picker (returns hex value) | https://formkit.com/inputs/color.md | | `range` | Slider range | https://formkit.com/inputs/range.md | | `date` | Date picker | https://formkit.com/inputs/date.md | | `datetime-local` | Date-time picker | https://formkit.com/inputs/datetime-local.md | | `button` | Button | https://formkit.com/inputs/button.md | | `email` | Email input | https://formkit.com/inputs/email.md | | `group` | Group (for grouping fields) | https://formkit.com/inputs/group.md | | `url` | URL input | https://formkit.com/inputs/url.md | FormKit Inputs overview: https://formkit.com/inputs FormKit Schema (conditional rendering, loops, expressions, advanced usage): https://formkit.com/essentials/schema.md > Note: FormKit Pro input components are not supported. ### Halo Extended Input Types Additional input components provided by Halo on top of FormKit: | `$formkit` value | Purpose | | ------------------ | ------------------------------------------------------------------- | | `select` | Enhanced dropdown with multi-select, search, and remote data source | | `switch` | Toggle switch (boolean or custom on/off values) | | `toggle` | Visual toggle supporting image/color/text options | | `attachment` | Attachment picker (preview, direct upload, select from library) | | `attachmentInput` | Attachment picker (library popup only) | | `code` | Code editor (supports yaml/html/js/css/json) | | `array` | Object array (add/remove/reorder — recommended over repeater) | | `list` | Primitive array (strings, numbers, etc.) | | `categorySelect` | Post category selector (returns `metadata.name`) | | `categoryCheckbox` | Post category checkbox (returns array of `metadata.name`) | | `tagSelect` | Post tag selector (returns `metadata.name`) | | `tagCheckbox` | Post tag checkbox (returns array of `metadata.name`) | | `postSelect` | Post selector | | `singlePageSelect` | Single page selector | | `menuSelect` | Menu selector (supports multi-select) | | `menuCheckbox` | Menu checkbox | | `menuRadio` | Menu radio | | `iconify` | Icon picker (Iconify-based, supports svg/dataurl/url/name formats) | | `secret` | Secret resource selector | | `verificationForm` | Remote verification form group | Full parameter reference: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/form-schema.md Version-sensitive FormKit notes: - Halo 2.25+: `select` option objects support `icon` and `description`; remote select data can map those fields via `requestOption.iconField` and `requestOption.descriptionField`. - Halo 2.25+: `secret` supports `descriptionPreset`. - Halo 2.23+: `iconify` supports optional `sizing`. - Halo 2.22.8+: `toggle` is available. - Halo 2.22.2+: `switch` is available. - Halo 2.22+: prefer `array` over `repeater`; the newer `attachment` supports preview/direct upload/library selection, while the older library-only picker is `attachmentInput`. ### FormKit Schema Gotchas **1. Inside `array` / `list` children, use `$value` — not `$get()`** Within the `children` of an `array`, `list`, or `repeater`, access the current item's data via `$value`, not `$get(id).value`. `$get()` can only reference standalone named input nodes by `id`; it cannot reach the current item in a nested context. ```yaml # ✅ Correct: use $value.[name] to reference a sibling field - $formkit: array name: socials children: - $formkit: text name: platform label: Platform - $formkit: text name: url label: URL if: "$value.platform !== ''" # references sibling field "platform" # ❌ Wrong: $get(platform).value does not work in a nested context - $formkit: text name: url if: "$get(platform).value !== ''" ``` **2. Nodes using `if` must also declare a `key`** Any `$formkit`/`$el`/`$cmp` node with an `if` attribute must declare a unique `key`. Without it, Vue reuses DOM nodes when the condition toggles, causing stale form values or rendering glitches. ```yaml # ✅ Correct: add key whenever if is present - $formkit: text key: url-field name: url label: URL if: "$value.show_link === true" # ❌ Wrong: missing key may cause value leakage on toggle - $formkit: text name: url label: URL if: "$value.show_link === true" ```