Input

Inputは、1行のテキストやファイルなどを入力するためのコンポーネントです。
プレースホルダーやラベル、ボタンとの組み合わせなど、さまざまなユースケースに対応しています。

プロパティ

Inputは、以下のプロパティをサポートしています。

名前 デフォルト値 説明
type string text 入力の種類を指定します。
placeholder string 入力欄に表示されるヒントテキストです。
readonly boolean false 読み取り専用の状態にします。編集はできません。
disabled boolean false 入力を無効化します。操作できません。
isError boolean false エラー状態を視覚的に示します。バリデーション用など。

インストールの手順

以下のコンポーネントのコードを、使いたいプロジェクトにコピー&ペーストします。
パスは実際のプロジェクトの構成にあわせて更新してください。

atoms/Input.svelte
        <!--
@component
## 概要
- 1行のテキストを入力する際に使用されるコンポーネントです

## 機能
- 見た目を変更するためのいくつかのスタイル用Propsが追加されています(詳細はPropsセクションを参照)

## Props
- type: inputのtypeを指定できます
- placeholder: プレースホルダーの文言を指定できます
- isError: true の場合エラー時のスタイルを適用します
- readonly: 指定すると読み取り専用の状態です
- disabled: 指定するとグレーアウトされ、クリック不可になります

## Usage
```svelte
<Input type="text" placeholder="プレースホルダー" bind:value />
```
-->

<script module lang="ts">
  import type { Snippet } from 'svelte';
  import type { ClassValue, HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
  import { cva, type VariantProps } from 'class-variance-authority';

  export const inputVariants = cva('relative w-full min-h-10 px-2.75 py-1.75 bg-base-container-default border border-base-stroke-default rounded-md text-base-foreground-default text-sm outline-primary transition-colors [&:not(:disabled):not([readonly])]:hover:bg-base-container-accent/90 active:border-base-stroke-default placeholder:text-base-foreground-muted focus-visible:outline-[0.125rem] focus-visible:outline-offset-[0.125rem] focus-visible:outline-primary', {
    variants: {
      /** type属性 */
      type: {
        text: [],
        file: ['text-base-foreground-muted file:px-2 file:text-base-foreground-default file:mr-0'],
      } as Partial<Record<HTMLInputTypeAttribute, string[]>>,
      /** 操作できるかどうか */
      disabled: {
        true: ['opacity-50 cursor-not-allowed'],
        false: [],
      },
      /** 読み取り専用かどうか */
      readonly: {
        true: ['opacity-50'],
        false: [],
      },
      /** エラーかどうか */
      isError: {
        true: ['border-destructive'],
        false: [],
      },
    },
    compoundVariants: [
      {
        type: 'file',
        readonly: true,
        class: 'cursor-not-allowed',
      },
    ],
    defaultVariants: {
      type: 'text',
      disabled: false,
      readonly: false,
    },
  });

  export const startContentVariants = cva('absolute inset-y-0 left-0 z-10 flex items-center pr-1 pl-3 pointer-events-none m-auto align-middle', {
    variants: {
      disabled: {
        true: ['cursor-not-allowed'],
        false: [],
      },
    },
    defaultVariants: {
      disabled: false,
    },
  });

  export const endContentVariants = cva('absolute inset-y-0 right-0 z-10 flex items-center pr-3 pl-1 pointer-events-none m-auto align-middle', {
    variants: {
      disabled: {
        true: ['cursor-not-allowed'],
        false: [],
      },
    },
    defaultVariants: {
      disabled: false,
    },
  });

  export type InputVariants = VariantProps<typeof inputVariants>;

  export interface InputProps extends InputVariants, HTMLInputAttributes {
    // inputのクラスを制御する値
    inputClass?: ClassValue;
    startContent?: Snippet;
    endContent?: Snippet;
    /** クラス */
    class?: ClassValue;
  }

  let inputElement = $state<HTMLElement>();

  export function focus() {
    if (inputElement) {
      inputElement?.focus();
    }
  }
</script>

<script lang="ts">
  let { isError = false, class: className, inputClass, value = $bindable(''), startContent, endContent, ...inputAttributes }: InputProps = $props();

  let startContentElement = $state<HTMLElement>();
  let endContentElement = $state<HTMLElement>();

  // 左側にコンテンツが来る場合の余白
  let inputLeftPadding = $derived.by(() => {
    if (startContentElement) {
      return startContentElement.clientWidth;
    }
  });

  // 右側にコンテンツが来る場合の余白
  let inputRightPadding = $derived.by(() => {
    if (endContentElement) {
      return endContentElement.clientWidth;
    }
  });

  let inputVariantClass = $derived(inputVariants({ type: inputAttributes.type, disabled: inputAttributes.disabled, readonly: inputAttributes.readonly, class: inputClass, isError }));

  let startContentVariantClass = $derived(startContentVariants({ disabled: inputAttributes.disabled }));

  let endContentVariantClass = $derived(endContentVariants({ disabled: inputAttributes.disabled }));
</script>

<div class={[className, 'relative z-0 inline-block w-full']}>
  {#if startContent}
    <div class={startContentVariantClass} bind:this={startContentElement}>
      <div class="contents pointer-events-auto">{@render startContent()}</div>
    </div>
  {/if}

  <input class={inputVariantClass} {...inputAttributes} bind:value bind:this={inputElement} style="padding-left: {inputLeftPadding}px; padding-right: {inputRightPadding}px" />

  {#if endContent}
    <div class={endContentVariantClass} bind:this={endContentElement}>
      <div class="contents pointer-events-auto">{@render endContent()}</div>
    </div>
  {/if}
</div>

<style>
  /* autofill用の考慮 */
  input:-webkit-autofill {
    box-shadow: 0 0 0 1000px rgb(255, 255, 255) inset !important;
    -webkit-text-fill-color: #18181b !important;
  }

  .dark input:-webkit-autofill {
    box-shadow: 0 0 0 1000px rgb(0, 9, 11) inset !important;
    -webkit-text-fill-color: #fafafa !important;
  }
</style>

      

使い方


サンプル

Text

基本的なテキスト入力の状態です。

Readonly

内容を編集できない、読み取り専用の状態です。

Error

入力内容に問題があり、エラーが表示されている状態です。

Disabled

利用不可の状態です。

With Label

入力フィールドとラベルを組み合わせた例です。

With Button

入力フィールドとボタンを組み合わせた例です。

With Icon

入力フィールドとアイコンを組み合わせた例です。

Input Password

入力フィールドをパスワード入力欄として利用した例です。

Max Length

入力可能な最大文字数を設定し、超えるとエラーが表示されます。