- Halo Plugin 后端(Java/Gradle),含 DarkModePlugin 主类和测试 - Vue 3 + TypeScript 前端 UI,包含主题切换组件和设置页面 - 暗色模式 CSS 变量和覆盖样式(布局/编辑器/表单/滚动条等) - 设计文档和调查文档 - Halo 插件/主题开发 Agent Skills Co-Authored-By: Claude <noreply@anthropic.com>
14 KiB
UI Forms (FormKit)
Halo uses FormKit as its form solution. FormKit is globally registered in both Console and UC (User Center) — you do not need to install or import FormKit in plugin UI code. Use <FormKit> components directly, or define forms via Schema in Setting resources.
Critical: Do NOT build custom form components from scratch (e.g. raw
<input>elements) in plugin pages. Always use FormKit inputs so your UI stays consistent with the rest of Halo.
Docs Routing
FormKit integration changes across Halo versions. Treat this file as a plugin working guide, then verify exact input options in the official docs when using a recent or version-sensitive field.
| Need | Official docs |
|---|---|
| Core form schema and built-in inputs | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/form-schema.md |
| Plugin custom FormKit inputs | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/formkit.md |
Plugin UI entry shape (formkit.inputs) |
https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/basics/ui/entry.md |
| Plugin API changelog for version gates | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-changelog.md |
| Business form components and directives | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/index.md |
| Annotation forms for extension metadata | https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/annotations-form.md |
When working from a local docs checkout, use the same paths under
docs/developer-guide/...; versioned docs live under
versioned_docs/version-2.25/....
Quick Index
- Plugin setting forms: Setting Schema
- Direct plugin page forms: Vue Component
- Built-in and Halo custom inputs: Available Inputs
- Version-sensitive fields: Version Notes
- Validation and schema patterns: Validation
- Submission patterns: Programmatic Form Submission
Two Ways to Use Forms
1. Setting Schema (Plugin Config)
For plugin settings that users configure in the plugin detail page, define the form in a Setting resource using FormKit Schema syntax (written in YAML):
# src/main/resources/extensions/settings.yaml
apiVersion: v1alpha1
kind: Setting
metadata:
name: my-plugin-settings # must match spec.settingName in plugin.yaml
spec:
forms:
- group: basic
label: Basic Settings
formSchema:
- $formkit: text
name: apiKey
label: API Key
value: ""
validation: required
- $formkit: switch
name: enabled
label: Enable Feature
value: true
Then reference it in plugin.yaml:
spec:
settingName: my-plugin-settings
configMapName: my-plugin-configmap
See plugin-manifest.md for full setup.
2. Vue Component (Direct <FormKit>)
For forms inside plugin pages (e.g. a custom admin page), use <FormKit> components directly:
<template>
<FormKit id="my-form" type="form" :actions="false" @submit="handleSubmit">
<FormKit type="text" name="title" label="Title" validation="required" />
<FormKit type="textarea" name="description" label="Description" :auto-height="true" />
<FormKit type="switch" name="published" label="Published" :value="true" />
<VButton type="primary" @click="$formkit.submit('my-form')"> Save </VButton>
</FormKit>
</template>
<script setup lang="ts">
import { Toast } from "@halo-dev/components";
function handleSubmit(values: Record<string, unknown>) {
console.log(values);
Toast.success("Saved");
}
</script>
No import { FormKit } from "@formkit/vue" is needed — FormKit is globally registered.
Available Inputs
FormKit Built-ins (Official)
All standard FormKit inputs work out of the box:
| Input | Type | Description |
|---|---|---|
text |
string |
Single-line text |
textarea |
string |
Multi-line text (with auto-height addon) |
email |
string |
Email with validation |
number |
number |
Numeric input |
password |
string |
Password (Halo disables autocomplete) |
date / datetime-local |
string |
Date pickers |
checkbox |
boolean / string[] |
Single or multi checkbox |
radio |
string |
Radio group |
range |
number |
Slider |
file |
FileList |
File input |
group |
object |
Nested object container |
Halo Custom Inputs
Halo registers additional inputs for common CMS use cases. Use them exactly like built-ins:
select — Enhanced Select
Custom select with static or remote data source, multi-select, sorting, and search.
- $formkit: select
name: country
label: Country
searchable: true
clearable: true
options:
- label: China
value: cn
icon: /assets/flags/cn.svg
description: Chinese cuisine with rich regional styles
- label: USA
value: us
Remote data source:
- $formkit: select
name: post
label: Post
clearable: true
action: /apis/api.console.halo.run/v1alpha1/posts
requestOption:
method: GET
labelField: post.spec.title
valueField: post.metadata.name
iconField: post.spec.cover
descriptionField: post.status.excerpt
Key props: options, action, requestOption, multiple, searchable, clearable, sortable, maxCount.
Halo 2.25+ supports icon and description in static options, plus
requestOption.iconField and requestOption.descriptionField for remote
options.
switch — Toggle Switch
- $formkit: switch
name: enabled
label: Enable
value: false
onValue: "active"
offValue: "inactive"
attachment / attachmentInput — Attachment Picker
attachment (Halo 2.22+): supports preview, direct upload, and library selection.
- $formkit: attachment
name: logo
label: Logo
accepts:
- "image/png"
- "image/jpeg"
width: "200px"
aspectRatio: "1/1"
attachmentInput: simpler input that opens the attachment library modal.
- $formkit: attachmentInput
name: cover
label: Cover
accepts: ["image/*"]
min: 1
max: 1
code — Code Editor
Integrated with CodeMirror. Supports yaml, html, javascript, css, json.
- $formkit: code
name: custom_css
label: Custom CSS
language: css
height: "300px"
iconify — Icon Selector
Based on Iconify.
- $formkit: iconify
name: social_icon
label: Social Icon
format: svg # svg | dataurl | url | name
With sizing options:
- $formkit: iconify
name: icon
label: Icon
format: svg
sizing:
enabled: true
default: "24"
presets: ["16", "24", "32", "48"]
toggle — Visual Toggle
For image, color, or text option toggling.
- $formkit: toggle
name: theme
label: Theme
render-type: color
options:
- label: Dark
value: dark
render: "#1a1a1a"
- label: Light
value: light
render: "#ffffff"
array — Object Array (Recommended over repeater)
For defining arrays of objects with add/remove/reorder.
- $formkit: array
name: socials
label: Social Accounts
value: []
max: 5
min: 1
itemLabels:
- type: image
label: $value.logo
- type: text
label: $value.name
children:
- $formkit: attachment
name: logo
label: Icon
- $formkit: text
name: name
label: Name
validation: required
- $formkit: text
name: url
label: URL
validation: required|url
Use
itemLabelsto show preview content on collapsed array items.$valuerefers to the current item.
list — Primitive Array
For arrays of primitives (strings, numbers, booleans).
- $formkit: list
name: tags
label: Tags
itemType: string
min: 1
max: 10
addLabel: Add Tag
children:
- $formkit: text
index: "$index"
validation: required
verificationForm — Remote Validation
Wraps a group of fields and validates them against a remote endpoint.
- $formkit: verificationForm
action: /apis/console.api.halo.run/v1alpha1/verify/verify-password
label: Verify Account
children:
- $formkit: text
name: username
label: Username
validation: required
- $formkit: password
name: password
label: Password
validation: required
Unlike other inputs,
verificationFormdoes NOT wrap values in its own key. The saved values stay flat:{ "username": "...", "password": "..." }.
CMS Entity Selectors
Halo provides dedicated selectors for core CMS entities. All return the resource's metadata.name.
| Input | Description | Multi-select |
|---|---|---|
menuSelect |
Navigation menu selector | Yes |
menuCheckbox |
Menu checkbox group | Yes (array) |
menuRadio |
Menu radio selection | No |
postSelect |
Post selector | No |
singlePageSelect |
Single page selector | No |
categorySelect |
Category selector | No |
categoryCheckbox |
Category checkbox | Yes (array) |
tagSelect |
Tag selector | No |
tagCheckbox |
Tag checkbox | Yes (array) |
userSelect |
User selector | Yes |
roleSelect |
Role selector | Yes |
attachmentGroupSelect |
Attachment group selector | Yes |
attachmentPolicySelect |
Attachment policy selector | Yes |
Example:
- $formkit: postSelect
name: featuredPost
label: Featured Post
value: ""
- $formkit: categoryCheckbox
name: categories
label: Categories
value: []
secret — Secret Resource Selector
For selecting a Halo Secret resource (stores sensitive data like API keys).
- $formkit: secret
name: apiSecret
label: API Secret
descriptionPreset: "Token for My Plugin"
requiredKeys:
- key: apiKey
help: API Key
- key: secretKey
help: Secret Key
color — Color Picker
- $formkit: color
name: themeColor
label: Theme Color
value: "#1890ff"
Custom Inputs from Plugin UI
Halo 2.25+ lets a plugin register custom FormKit inputs from ui/src/index.ts
through definePlugin({ formkit: { inputs } }). See ui-entry.md.
Use these only when built-in FormKit and Halo inputs cannot express the
interaction. Prefix names with the plugin identifier to avoid collisions, for
example myPluginTokenPicker.
Version Notes
- Halo 2.25+ supports
selectoptionicon/description, remoteiconField/descriptionField, and plugin-registered custom FormKit inputs. - When using 2.25-only FormKit features, raise
spec.requiresinplugin.yamland keep related UI packages on a compatible version.
Programmatic Form Submission
In Vue components, trigger form submission programmatically:
<VButton type="primary" @click="$formkit.submit('my-form-id')">
Submit
</VButton>
Or using @formkit/core:
import { submitForm } from "@formkit/core";
submitForm("my-form-id");
Validation
FormKit supports built-in validation rules. Use them in Schema or Vue components:
- $formkit: text
name: email
label: Email
validation: required|email
<FormKit
type="text"
name="slug"
label="Slug"
:validation="[['required'], ['matches', /^[a-z0-9-]+$/]]"
/>
Common rules: required, email, url, number, min, max, matches, confirm.
Conditional Rendering
Use if in Schema to conditionally show fields:
- $formkit: select
name: type
label: Type
options:
- label: Internal
value: internal
- label: External
value: external
- $formkit: text
name: url
label: URL
if: "$value.type === 'external'"
validation: required|url
In
ifexpressions,$valuerefers to the current form values object.
Schema vs Vue Component: When to Use Which
| Scenario | Approach |
|---|---|
| Plugin settings (config page) | Setting resource with Schema |
| Custom admin page with dynamic logic | Vue <FormKit> components |
| Simple CRUD form in a modal | Vue <FormKit> components |
| Reusable form across plugins | Vue <FormKit> components in a shared package |
Important Notes
- Do NOT install FormKit in your plugin — it's already globally registered. Installing it again can cause conflicts.
- Do NOT use FormKit Pro inputs — they are not included in Halo.
- Schema is JSON format natively, but Halo uses YAML for
Settingresources. Write Schema in YAML syntax. - When using
arrayorlist, always providevalue: []as default to avoid undefined issues. - For
attachmentwithmultiple: true, the value is astring[]of attachment URLs/names.