Custom Components Documentation¶
Guide to using custom form components and UI elements in Meister Bill
Table of Contents¶
Form Components¶
All form components are built on top of Formwerk, a lightweight form validation library with full TypeScript support.
FCheckbox¶
Location: apps/web/components/F/Checkbox.vue
A checkbox component with Formwerk integration, error handling, and support for custom label content.
Basic Usage¶
<template>
<form>
<!-- Simple checkbox with label prop -->
<FCheckbox
label="Primary Address"
name="is_primary"
/>
<!-- Checkbox with validation -->
<FCheckbox
label="Accept Newsletter"
name="newsletter_accepted"
/>
</form>
</template>
Advanced Usage with Slot¶
The FCheckbox component supports a default slot for custom label content, such as labels with inline links:
<template>
<form>
<!-- Checkbox with link in label -->
<FCheckbox name="terms_accepted">
{{ $t('label.i_accept_the') }}
<NuxtLink
:to="$localePath('info-terms-of-use')"
target="_blank"
class="link link-primary"
>
{{ $t('label.terms_and_conditions') }}
</NuxtLink>
</FCheckbox>
<!-- Checkbox with formatted content -->
<FCheckbox name="subscribe">
Subscribe to our newsletter and get
<strong>10% off</strong> your first invoice!
</FCheckbox>
</form>
</template>
Props¶
| Prop | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Form field name |
label |
string |
No | Simple text label (not used when slot is provided) |
Other props from Formwerk's CheckboxProps |
See Formwerk Checkbox docs |
Slots¶
| Slot | Description |
|---|---|
default |
Custom label content. If provided, the label prop is ignored. |
Features¶
- ✅ Formwerk integration for validation
- ✅ Error message display
- ✅ Dark mode support
- ✅ DaisyUI styling
- ✅ Custom label content via slot
- ✅ Backward compatible (label prop still works)
Implementation Details¶
The component checks for the presence of a default slot:
- If slot exists: Renders slot content
- If no slot: Falls back to label prop
This ensures backward compatibility with existing code while enabling new use cases.
FTextField¶
Location: apps/web/components/F/TextField.vue
A text input component with Formwerk integration.
Basic Usage¶
<FTextField
label="Email"
name="email"
type="email"
:placeholder="$t('label.email')"
/>
<FTextField
label="Password"
name="password"
type="password"
:placeholder="$t('label.password')"
/>
FErrorMessage¶
Location: apps/web/components/F/ErrorMessage.vue
Displays validation error messages in a consistent style.
Usage¶
Error messages are automatically displayed by form components when validation fails. The component is used internally by FCheckbox, FTextField, and other form components.
Component Patterns¶
Form Integration with Formwerk¶
All custom form components use Formwerk's composables:
<script setup lang="ts">
import { type CheckboxProps, useCheckbox } from "@formwerk/core";
const props = defineProps<CheckboxProps>();
const { labelProps, inputProps, errorMessage, isTouched } = useCheckbox(props);
</script>
Schema Validation¶
Forms use Zod schemas for validation:
import { SignUpSchema } from "@meisterbill/schemas";
const { handleSubmit, values } = useForm<SignUp>({
schema: SignUpSchema,
initialValues: props.initialValues || {},
});
Example schema with checkbox validation:
export const SignUpSchema = z.object({
email: z.string().email(),
password: PasswordSchema,
terms_accepted: z.literal(true, { error: "accept_terms" }),
});
Best Practices¶
1. Use Slots for Complex Labels¶
When labels need inline links, formatting, or dynamic content:
<!-- ✅ Good: Use slot -->
<FCheckbox name="terms">
I accept the <NuxtLink to="/terms">Terms</NuxtLink>
</FCheckbox>
<!-- ❌ Avoid: Complex HTML in label prop -->
<FCheckbox :label="`I accept the <a href='/terms'>Terms</a>`" name="terms" />
2. Leverage i18n for Labels¶
Always use i18n for user-facing text:
<FCheckbox name="newsletter">
{{ $t('label.subscribe_to') }}
<strong>{{ $t('label.newsletter') }}</strong>
</FCheckbox>
3. Validation Messages¶
Define validation errors in the schema with i18n keys:
terms_accepted: z.literal(true, { error: "accept_terms" })
Then add the translation:
{
"message": {
"accept_terms": "You must accept the terms and conditions"
}
}
4. Consistent Styling¶
Use DaisyUI classes for consistent styling:
<NuxtLink to="/terms" class="link link-primary">
Terms and Conditions
</NuxtLink>
5. Accessibility¶
- Always provide meaningful labels or slot content
- Use semantic HTML elements
- Ensure links open in new tabs when appropriate with
target="_blank"
Examples¶
Sign-Up Form with Terms Acceptance¶
<template>
<form @submit="onSubmit">
<FTextField
label="Name"
name="name"
type="text"
:placeholder="$t('label.name')"
/>
<FTextField
label="Email"
name="email"
type="email"
:placeholder="$t('label.email')"
/>
<FTextField
label="Password"
name="password"
type="password"
:placeholder="$t('label.password')"
/>
<FCheckbox name="terms_accepted">
{{ $t('label.i_accept_the') }}
<NuxtLink
:to="$localePath('info-terms-of-use')"
target="_blank"
class="link link-primary"
>
{{ $t('label.terms_and_conditions') }}
</NuxtLink>
</FCheckbox>
<button type="submit" class="btn btn-primary">
{{ $t('label.sign_up') }}
</button>
</form>
</template>
<script setup lang="ts">
import { type SignUp, SignUpSchema } from "@meisterbill/schemas";
import { useForm } from "@formwerk/core";
const { handleSubmit, values } = useForm<SignUp>({
schema: SignUpSchema,
initialValues: {},
});
const onSubmit = handleSubmit((data) => {
// Handle form submission
console.log(data.toObject());
});
</script>
Migration Guide¶
Updating Existing Checkboxes to Support Links¶
Before:
<fieldset class="fieldset">
<label class="label">
<input type="checkbox" name="terms" />
I accept the <a href="/terms">Terms</a>
</label>
</fieldset>
After:
<FCheckbox name="terms">
I accept the <NuxtLink to="/terms">Terms</NuxtLink>
</FCheckbox>
Related Documentation¶
Last updated: 2025-10-05