Skip to content
ShineUI
Install

FloatLabel

The label rides the border.

<sui-float-label>Text inputsStable

About

Text field whose label sits in the placeholder position and floats up onto the border on focus or when there is a value. For marketing and sign-in forms; in-app forms use sui-input-text with its static label. Form-associated: it posts name=value and binds to React, Vue (v-model) and Angular forms.

Usage

<sui-float-label label="Email" name="email" type="email" autocomplete="username"></sui-float-label> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-float-label"); el.addEventListener("suiChange", (e) => console.log(e.detail)); </script>
import { SuiFloatLabel } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiFloatLabel label="Email" name="email" type="email" autocomplete="username" onSuiChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiFloatLabel } from "@shineui/vue"; import "@shineui/core/shineui.css"; const onChange = (e) => console.log(e.detail); </script> <template> <SuiFloatLabel label="Email" name="email" type="email" autocomplete="username" @sui-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiFloatLabel } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiFloatLabel], template: `<sui-float-label label="Email" name="email" type="email" autocomplete="username" (suiChange)="onChange($event)"></sui-float-label>`, }) export class Example { onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

The label starts where a placeholder would sit and floats onto the border on focus or once there is a value. It is a real label bound to the input, so clicking it focuses the field.

<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;max-width:480px"> <sui-float-label label="Email" name="email" type="email" autocomplete="username"></sui-float-label> <sui-float-label label="Contact" value="Rhonda Mills"></sui-float-label> </div>

States

The floating label is the field label, so help and error render on the shared lines below. The error line carries role="alert" and paints the label too.

<div style="display:grid;gap:14px;max-width:340px"> <sui-float-label label="Email" help="We never share it." value="rhonda@silverhills.org"></sui-float-label> <sui-float-label label="Password" type="password" error="Email or password is incorrect."></sui-float-label> <sui-float-label label="Account" value="Silver Hills Church" readonly help="Set when the account was created."></sui-float-label> <sui-float-label label="Notes" maxlength="60" value="Net 30 terms." disabled></sui-float-label> </div>

In a form

Each field posts one entry under its host name. required blocks the submit while a field is empty, and the label names the browser message.

Submit to see what posts.
<form id="fl-form" style="display:grid;gap:14px;max-width:340px"> <sui-float-label label="Email" name="email" type="email" required autocomplete="username"></sui-float-label> <sui-float-label label="Password" name="password" type="password" required autocomplete="current-password"></sui-float-label> <sui-button type="submit" variant="brand" label="Sign in"></sui-button> <output id="fl-out" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Submit to see what posts.</output> </form>
// setup(root): properties and events setup(root) { const form = root.querySelector('#fl-form'); const out = root.querySelector('#fl-out'); form.addEventListener('submit', (e) => { e.preventDefault(); out.textContent = [...new FormData(form)].map(([k, v]) => `${k}=${k === 'password' ? '(hidden)' : v || '(empty)'}`).join(' '); }); }

Events

suiInput fires on every keystroke, suiChange on commit at blur or Enter. Both carry the current value as the detail. suiFocus fires as the label floats up and suiBlur as the input lets go.

Idle.
<div style="display:grid;gap:10px;max-width:340px"> <sui-float-label id="fl-events" label="Company name" name="company"></sui-float-label> <span id="fl-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Idle.</span> </div>
// setup(root): properties and events setup(root) { const log = root.querySelector('#fl-log'); const el = root.querySelector('#fl-events'); el.addEventListener('suiFocus', () => { log.textContent = 'suiFocus'; }); el.addEventListener('suiBlur', () => { log.textContent = 'suiBlur'; }); el.addEventListener('suiInput', (e) => { log.textContent = `suiInput ${e.detail || '(empty)'}`; }); el.addEventListener('suiChange', (e) => { log.textContent = `suiChange ${e.detail || '(empty)'}`; }); }

Keyboard

KeyAction
TabMoves to the field and on to the next control; the host itself takes no tab stop.
Printable charactersType into the native input; suiInput fires on every keystroke.
EnterSubmits the surrounding form, as a native input does.

API

<sui-float-label>

Properties

PropertyAttributeTypeDefaultDescription
autocompleteautocompletestringstring | undefinedNative autocomplete hint. inputmode and enterkeyhint set on the host reach the input the same way, and spellcheck and autocapitalize on the host apply to it as they do to any descendant.
disableddisabledbooleanfalseDisables the field.
errorerrorstringstring | undefinedError message on the shared line under the box; paints the field and label in the danger tone and sets aria-invalid.
helphelpstringstring | undefinedHelp text on the shared line under the box. Replaced by error when set.
invalidinvalidbooleanfalseDanger border and label.
label*labelstringThe floating label. It takes the placeholder's position, so there is no placeholder prop, and it names the field; only without one does the host's aria-label or aria-labelledby, the rule every text field shares.
maxLengthmaxlengthnumbernumber | undefinedMaximum number of characters, as the native maxlength.
minLengthminlengthnumbernumber | undefinedNative minlength: a value shorter than this, once the user has typed, fails as tooShort.
namenamestringstring | undefinedForm control name. Reflected: form entry construction reads the content attribute, while all three framework bindings set properties, so an unreflected name posts nothing at all.
patternpatternstringstring | undefinedNative pattern: a regular expression the whole value must match. The browser checks it and the field reports patternMismatch on its validity, the way required reports valueMissing.
readOnlyreadonlybooleanfalseDisplay only: the text can be selected and copied but not edited; the field still posts its value.
requiredrequiredbooleanfalseMarks the field required (valueMissing validity when empty).
typetypeSuiInputType"email" | "password" | "search" | "tel" | "text" | "url"'text'Native input type. One union across the four elements that wrap a native text input.
valuevaluestring''Current value. Mutable; listen to suiInput / suiChange to observe edits.

Events

EventDetailReactVueDescription
suiBlurvoidonSuiBlur@sui-blurFires when the input loses focus; no detail. Does not bubble.
suiChangestringonSuiChange@sui-changeFires on commit (blur or Enter, native change); detail is the final value.
suiFocusvoidonSuiFocus@sui-focusFires when the input takes focus, as the label floats up; no detail. The same pair is on every text field. Does not bubble.
suiInputstringonSuiInput@sui-inputFires on every keystroke; detail is the current value.

Methods

MethodSignatureDescription
select()select() => Promise<void>Selects the field text.
setFocus()setFocus() => Promise<void>Moves keyboard focus into the field.

Types

SuiInputType typeThe native type values the text-input skin draws. One union for the whole family: the four elements that wrap a native text input each listed a different subset in prose, which read as a rule and was not one. Anything outside this set has its own element (sui-input-number, sui-date-picker, sui-color-picker) or its own skin.
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also