N
Nuxt2mo ago
ISOREX

Extending UInput and UFormField

Hi everyone, I'm investigating if it's worth switching over to Nuxt UI from Reka UI. There are a few components however where my design doesn't overlap with that of Nuxt UI. One of these cases is the input and form field. I'm wondering if there is a recommended way of extending these components? For FormField I copied over the source code and started from there (main difference is that the validation errors are shown in a popover and the label and input are inline instead of stacked). For the input I didn't get much further than wrapping Input and adding v-bind="$props". The reason I have to extend Input is because it needs to inject the blur and focus events. How would you guys implement this? I have provided my current snippets below.
No description
8 Replies
kapa.ai
kapa.ai2mo ago
Hi @ISOREX. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Nuxt UI provides a recommended way to integrate custom inputs with the UForm and UFormField components using the useFormField composable. This composable exposes helpers like inputId, emitFormBlur, emitFormInput, and emitFormChange that you can use inside your custom input components to ensure they work seamlessly with Nuxt UI's form validation and error handling system. For custom Input components: - Use the useFormField composable in your custom input to emit the necessary events (blur, input, change) so that validation and error handling work as expected. - This approach is preferred over copying the source code, as it keeps your component compatible with Nuxt UI's form system and future updates. Example usage:
<script setup lang="ts">
const { inputId, emitFormBlur, emitFormInput, emitFormChange } = useFormField()
// Use these helpers in your custom input logic
</script>
<script setup lang="ts">
const { inputId, emitFormBlur, emitFormInput, emitFormChange } = useFormField()
// Use these helpers in your custom input logic
</script>
See composable usage For custom FormField components: - If your design requires a different layout (e.g., inline label and input, errors in a popover), you can create your own wrapper component. - You can still use the name prop and emit errors in the same format as Nuxt UI expects, so your custom field works with the UForm validation system. - If you need to show errors differently, you can listen to the error events or use the error slot/prop as needed. Summary of best practices: - Do not copy the source code unless you need a completely custom implementation. - Wrap and extend using the useFormField composable for custom inputs. - Emit the correct events (input, blur, change) for validation to work. - Use the name prop to associate errors with fields. This approach keeps your custom components compatible with Nuxt UI's form system and leverages its validation and error handling features without forking or duplicating core logic. For more details, see the useFormField composable documentation and the Form input events section. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@193378271700713472> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
ISOREX
ISOREXOP2mo ago
HInput.vue
<script setup lang="ts">
import { inject } from "vue";

const showPopover = inject('showPopover', false)
</script>

<template>
<UInput v-bind="$props" @focus="showPopover = true" @blur="showPopover = false"/>
</template>
<script setup lang="ts">
import { inject } from "vue";

const showPopover = inject('showPopover', false)
</script>

<template>
<UInput v-bind="$props" @focus="showPopover = true" @blur="showPopover = false"/>
</template>
ISOREX
ISOREXOP2mo ago
HFormField.vue
ISOREX
ISOREXOP2mo ago
Usage
<script lang="ts" setup>
import * as v from "valibot";
import type { FormSubmitEvent } from "@nuxt/ui";
import { reactive, ref } from "vue";

const schema = v.object({
email: v.pipe(v.string(), v.email("Invalid email")),
password: v.pipe(v.string(), v.minLength(8, "Must be at least 8 characters")),
serial: v.pipe(v.number(), v.minValue(0, "The number must be higher than 0"), v.maxValue(999, "The number must be below 999"))
});

type Schema = v.InferOutput<typeof schema>;

const state = reactive({
email: "",
password: "",
serial: 0,
});

const toast = useToast();

async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({
title: "Success",
description: "The form has been submitted.",
color: "success",
});
console.log(event.data);
}

const open = ref(false);
</script>

<template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit" >
<span class="text-dimmed pl-2 text-xs">FORM</span>
<UCard
:ui="{ body: 'divide-y-1 divide-muted sm:p-0 *:px-2 *:py-0.5' }"
class="w-fit"
variant="solid"
>
<HFormField :show-popover="open" label="Email" name="email">
<HInput
v-model="state.email"
variant="ghost"
/>
</HFormField>
<HFormField :show-popover="open" label="Password" name="password">
<HInput
v-model="state.password"
variant="ghost"
/>
</HFormField>
<HFormField :show-popover="open" label="Serial" name="serial">
<HInput
v-model="state.serial"
variant="ghost"
type="number"
/>
</HFormField>
</UCard>
</UForm>
</template>
<script lang="ts" setup>
import * as v from "valibot";
import type { FormSubmitEvent } from "@nuxt/ui";
import { reactive, ref } from "vue";

const schema = v.object({
email: v.pipe(v.string(), v.email("Invalid email")),
password: v.pipe(v.string(), v.minLength(8, "Must be at least 8 characters")),
serial: v.pipe(v.number(), v.minValue(0, "The number must be higher than 0"), v.maxValue(999, "The number must be below 999"))
});

type Schema = v.InferOutput<typeof schema>;

const state = reactive({
email: "",
password: "",
serial: 0,
});

const toast = useToast();

async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({
title: "Success",
description: "The form has been submitted.",
color: "success",
});
console.log(event.data);
}

const open = ref(false);
</script>

<template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit" >
<span class="text-dimmed pl-2 text-xs">FORM</span>
<UCard
:ui="{ body: 'divide-y-1 divide-muted sm:p-0 *:px-2 *:py-0.5' }"
class="w-fit"
variant="solid"
>
<HFormField :show-popover="open" label="Email" name="email">
<HInput
v-model="state.email"
variant="ghost"
/>
</HFormField>
<HFormField :show-popover="open" label="Password" name="password">
<HInput
v-model="state.password"
variant="ghost"
/>
</HFormField>
<HFormField :show-popover="open" label="Serial" name="serial">
<HInput
v-model="state.serial"
variant="ghost"
type="number"
/>
</HFormField>
</UCard>
</UForm>
</template>
@kapa.ai I agree copying source code is a bad idea, but my issue lies with extending/ changing FormField. useFormField seems to be for adding additional inputs.
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- You're correct that useFormField is intended for integrating custom input components with the Nuxt UI form system, not for extending or changing the layout/behavior of UFormField itself. Based on the knowledge sources, there is no officially documented method for deeply extending or customizing the internal structure or layout of UFormField beyond what is exposed via props (like label, description, hint, help, error, size, etc.) and the ui prop for class overrides. The theming system allows you to adjust classes for different slots and variants globally via app.config.ts, but it does not provide a slot-based API for changing the markup order (e.g., making label and input inline, or rendering errors in a popover) from outside the component itself. The available slots are for customizing content, not structure or placement FormField API. If your requirements (like inline label/input or popover errors) cannot be achieved with the available props, theming, or class overrides, the knowledge sources do not describe a recommended way to extend or deeply customize UFormField. In such cases, creating your own wrapper component, as you have done, is the only approach shown—though this does mean you are responsible for maintaining compatibility with the Nuxt UI form system (e.g., handling errors, validation, etc.) FormField Usage. Summary:
- useFormField is for custom inputs, not for changing UFormField structure. - Theming and the ui prop allow for class-level customization, not markup changes. - There is no documented slot-based or extension API for changing the structure of UFormField. - If you need a fundamentally different layout, a custom wrapper is the only approach shown in the docs. If you need to maintain compatibility with Nuxt UI's validation and error system, ensure your custom component uses the same name prop conventions and error handling as UFormField does. I'm sorry, but the knowledge sources do not describe a more granular or officially recommended way to extend or restructure UFormField beyond these options. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@193378271700713472> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
ISOREX
ISOREXOP2mo ago
Alright, thanks. Hopefully a real human can give some recommendation on what would be a good solution to this. Perhaps sticking with Reka UI and using useFormField to keep the Form compatibility would be the best way here?

Did you find this page helpful?