Skip to content

Component v-model

TextField:

vue
<script lang="ts" setup>
import type { InputHTMLAttributes } from 'vue';
import { computed } from 'vue';
import uniqueId from 'lodash/uniqueId';

interface Props extends InputHTMLAttributes {
  label?: string;
  value?: string;
}

const props = defineProps<Props>();

const emit = defineEmits<{
  (evt: 'update:value', val: string): void;
}>();

const uid = uniqueId('text-field-');

const textFieldValue = computed({
  get: () => props.value || '',
  set: (val) => emit('update:value', val),
});
</script>

<template>
  <div class="text-field">
    <label :for="uid">{{ label }}</label>
    <input :id="uid" v-model="textFieldValue" v-bind="$attrs" />
  </div>
</template>

<style lang="scss" scoped>
.text-field {
  @apply flex flex-col w-full;
}
</style>
vue
<script lang="ts" setup>
import type { InputHTMLAttributes } from 'vue';
import uniqueId from 'lodash/uniqueId';

interface Props extends InputHTMLAttributes {
  label?: string;
}

const props = defineProps<Props>();

const value = defineModel<string>('value', { default: '' });

const uid = uniqueId('text-field-');
</script>

<template>
  <div class="text-field">
    <label :for="uid">{{ label }}</label>
    <input :id="uid" v-model="value" v-bind="$attrs" />
  </div>
</template>

<style lang="scss" scoped>
.text-field {
  @apply flex flex-col w-full;
}
</style>
svelte
<script lang="ts">
  import type { HTMLInputAttributes } from 'svelte/elements';
  import uniqueId from 'lodash/uniqueId';

  interface $$Props extends HTMLInputAttributes {
    label?: string;
    value?: string;
  }

  export let label = '';
  export let value = '';

  const uid = uniqueId('text-field-');
</script>

<div class="text-field">
  <label for={uid}>{label}</label>
  <input id={uid} class="{$$props.class}" bind:value {...$$restProps} />
</div>

<style lang="scss">
  .text-field {
    --at-apply: flex flex-col w-full;
  }
</style>
tsx
import type { ComponentPropsWithoutRef } from 'react';
import uniqueId from 'lodash/uniqueId';

export interface TextFieldProps extends ComponentPropsWithoutRef<'input'> {
  label?: string;
  value?: string;
  onInput(val: string): void;
}

export function TextField(props: TextFieldProps) {
  const { label, value, onInput, ...rest } = props;

  const uid = uniqueId('text-field-');

  return (
    <div class="text-field">
      <label for={uid}>{label}</label>
      <input
        id={uid}
        value={value}
        onInput={(evt) => onInput((evt.target as HTMLInputElement).value)}
        {...rest}
      />
    </div>
  );
}

Usage:

vue
<script lang="ts" setup>
import { reactive } from 'vue';

import TextField from '~/components/TextField.vue';

const store = reactive({
  val1: '',
  val2: '',
  val3: '',
  blurVal3() {
    console.log('blurVal3');
  },
});
</script>

<template>
  <TextField v-model:value="store.val1" />
  <TextField v-model:value="store.val2" class="max-w-36" />
  <TextField v-model:value="store.val3" @blur="store.blurVal3" />
</template>
svelte
<script lang="ts">
  import { writable } from 'svelte/store';

  import TextField from '$lib/components/TextField.svelte';

  const state = writable({
    val1: '',
    val2: '',
    val3: '',
  });

  const actions = {
    blurVal3() {
      console.log('blurVal3');
    },
  };
</script>

<TextField bind:value={state.val1} />
<TextField bind:value={state.val2} class="max-w-36" />
<TextField bind:value={state.val3} on:blur={actions.blurVal3} />
tsx
import { useSignal } from '@preact/signals';

import { TextField } from '~/components/TextField';

export function App() {
  const val1 = useSignal('');
  const val2 = useSignal('');
  const val3 = useSignal('');

  const blurVal3 = () => {
    console.log('blurVal3');
  };

  return (
    <>
      <TextField value={val1.value} onInput={(val) => (val1.value = val)} />
      <TextField value={val2.value} onInput={(val) => (val2.value = val)} class="max-w-36" />
      <TextField value={val3.value} onInput={(val) => (val3.value = val)} onBlur={blurVal3} />
    </>
  );
}

Released under the MIT License.