Skip to content
edmi-ui

Input

Single-line text field on the card surface with a focus ring; elevation adds sunken, raised and floating depth.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
Terminal window
npx shadcn@latest add @edmi-ui/input
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/input
Terminal window
yarn dlx shadcn@latest add @edmi-ui/input
Terminal window
bunx shadcn@latest add @edmi-ui/input

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/input
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/input
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/input
Terminal window
bunx shadcn-vue@latest add @edmi-ui/input

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/input.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/input.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/input.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/input.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

import { Input } from "@/components/ui/input";
<div className="grid w-full max-w-sm gap-3">
<Input placeholder="Magnificent Four" />
<Input defaultValue="MAG4" />
<Input placeholder="Invalid" aria-invalid defaultValue="mag 4" />
<Input placeholder="Disabled" disabled />
<Input type="file" />
</div>
<script setup lang="ts">
import { Input } from "@/components/ui/input";
</script>
<template>
<div class="grid w-full max-w-sm gap-3">
<Input placeholder="Magnificent Four" />
<Input default-value="MAG4" />
<Input placeholder="Invalid" aria-invalid="true" default-value="mag 4" />
<Input placeholder="Disabled" disabled />
<Input type="file" />
</div>
</template>
<script lang="ts">
import { Input } from "$lib/components/ui/input";
</script>
<div class="grid w-full max-w-sm gap-3">
<Input placeholder="Magnificent Four" />
<Input />
<Input placeholder="Invalid" aria-invalid="true" value="mag 4" />
<Input placeholder="Disabled" disabled />
<Input type="file" />
</div>
registry/ui/input.tsx
"use client";
import { Input as InputPrimitive } from "@base-ui/react/input";
import { cn } from "cn";
import type * as React from "react";
import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
// ✦ depth (v4): fields sink (-1) in layered mode; focus swaps the edge for the ring
const fieldElevation = {
sunken: "border-sk-bd bg-sk-bg shadow-sunken focus-visible:bg-card",
flat: "",
raised:
"border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-raised focus-visible:border-ring focus-visible:shadow-ring",
floating:
"border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-floating focus-visible:border-ring focus-visible:shadow-ring",
};
function Input({
className,
type,
elevation,
...props
}: React.ComponentProps<"input"> & {
/** ✦ depth: sunken -1, flat 0, raised +1, floating +2. */
elevation?: Elevation;
}) {
const level = useElevation(elevation, "field");
return (
<InputPrimitive
type={type}
data-slot="input"
className={cn(
"flex h-9 w-full min-w-0 items-center gap-2 rounded-md border border-input bg-card px-3 text-sm text-foreground outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:shadow-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-ring-error data-[invalid]:border-destructive data-[invalid]:shadow-ring-error",
fieldElevation[level],
className,
)}
{...props}
/>
);
}
export { Input };
registry/ui/input/Input.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { useVModel } from '@vueuse/core'
import { cn } from '@/registry/edmi/lib/utils'
import { type Elevation, useElevation } from '@/registry/edmi/ui/elevation'
const props = withDefaults(defineProps<{
defaultValue?: string | number
modelValue?: string | number
/** ✦ depth: sunken -1, flat 0, raised +1, floating +2 */
elevation?: Elevation
class?: HTMLAttributes['class']
}>(), { elevation: undefined })
const emits = defineEmits<{
(e: 'update:modelValue', payload: string | number): void
}>()
const modelValue = useVModel(props, 'modelValue', emits, {
passive: true,
defaultValue: props.defaultValue,
})
// ✦ depth (v4): fields sink (-1) in layered mode; focus swaps the edge for the ring
const fieldElevation = {
sunken: 'border-sk-bd bg-sk-bg shadow-sunken focus-visible:bg-card',
flat: '',
raised: 'border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-raised focus-visible:border-ring focus-visible:shadow-ring',
floating: 'border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-floating focus-visible:border-ring focus-visible:shadow-ring',
}
const level = useElevation(() => props.elevation, 'field')
</script>
<template>
<input
v-model="modelValue"
data-slot="input"
:class="cn(
'flex h-9 w-full min-w-0 items-center gap-2 rounded-md border border-input bg-card px-3 text-sm text-foreground outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:shadow-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-ring-error',
fieldElevation[level],
props.class,
)"
>
</template>
registry/ui/input/index.ts
export { default as Input } from "./Input.vue";
src/lib/registry/ui/input/input.svelte
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js";
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
type Props = WithElementRef<
Omit<HTMLInputAttributes, "type"> &
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
> & {
/** ✦ depth: sunken -1, flat 0, raised +1, floating +2. */
elevation?: Elevation;
};
// ✦ depth (v4): fields sink (-1) in layered mode; focus swaps the edge for the ring
const fieldElevation = {
sunken: "border-sk-bd bg-sk-bg shadow-sunken focus-visible:bg-card",
flat: "",
raised: "border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-raised focus-visible:border-ring focus-visible:shadow-ring",
floating: "border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-floating focus-visible:border-ring focus-visible:shadow-ring",
};
let {
ref = $bindable(null),
value = $bindable(),
type,
files = $bindable(),
class: className,
elevation = "auto",
"data-slot": dataSlot = "input",
...restProps
}: Props = $props();
const level = useElevation(() => elevation, "field");
</script>
{#if type === "file"}
<input
bind:this={ref}
data-slot={dataSlot}
class={cn(
"flex h-9 w-full min-w-0 items-center gap-2 rounded-md border border-input bg-card px-3 text-sm text-foreground outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:shadow-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-ring-error",
fieldElevation[level.current],
className
)}
type="file"
bind:files
bind:value
{...restProps}
/>
{:else}
<input
bind:this={ref}
data-slot={dataSlot}
class={cn(
"flex h-9 w-full min-w-0 items-center gap-2 rounded-md border border-input bg-card px-3 text-sm text-foreground outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:shadow-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-ring-error",
fieldElevation[level.current],
className
)}
{type}
bind:value
{...restProps}
/>
{/if}
src/lib/registry/ui/input/index.ts
import Root from "./input.svelte";
export {
Root,
//
Root as Input,
};

See the Elevation guide for the levels, roles and layered mode.

Fields are flat by default and sink (-1) in layered mode. sunken shows a soft inset well (focus swaps the edge for the ring), raised (+1) a bevel and floating (+2) a bevel with a soft drop.

StackBlitz ↗StackBlitz ↗StackBlitz ↗

Sunken (-1)

Flat (0)

Raised (+1)

Floating (+2)

Sunken (-1)

Flat (0)

Raised (+1)

Floating (+2)

Sunken (-1)

Flat (0)

Raised (+1)

Floating (+2)

Prop Type Default
Props React.ComponentProps<"input"> (Base UI Input) none
aria-invalid boolean false
elevation "auto" | "sunken" | "flat" | "raised" | "floating" "auto"
  • No size prop; the state comes from disabled and aria-invalid.
  • A ring halo on focus and error; the sunken level swaps its edge for the ring on focus.