Input

Form element that allows users to input and edit text or data into a website page.

Class name Type
.prs-input Component For <input> or <label>
.prs-input-ghost Modifier Remove border
.prs-input_focus State Manually apply visual focus
.prs-input_invalid State Manually apply visual invalid
.prs-input_disabled State Manually apply visual disabled
  • <label><input type="text" class="prs-input" placeholder="Placeholder" aria-label="Text" /></label>
    
  • <label><textarea class="prs-input" placeholder="Placeholder" aria-label="Textarea"></textarea></label>
    
  • <label>
      <select class="prs-input" aria-label="Select">
        <option selected disabled>Choose:</option>
        <option>1</option>
        <option>2</option>
        <option>3</option>
      </select>
    </label>
    
  • <div class="w-96 max-w-xs space-y-5"> <!-- <- this is just for demo purposes -->
    
      <label><input type="range" min="0" max="100" value="50" class="prs-input" aria-label="Range" /></label>
    
      <div>
        <label><input type="range" min="1" max="5" step="1" value="2" class="prs-input" aria-label="Range" /></label>
        <div class="p-1 w-full text-(gray-500 xs) font-mono [font-variant-numeric:tabular-nums] flex justify-between pointer-events-none select-none" aria-hidden="true"> <!-- <- this is just for demo purposes -->
          <span>1</span><span>2</span><span>3</span><span>4</span><span>5</span>
        </div>
      </div>
    
    </div>
    
  • <label class="prs-form-control">
      <div class="prs-label">
        <span class="prs-label-text">Upload image</span>
      </div>
      <input type="file" class="prs-input" accept="image/*" />
    </label>
    
  • Container

    The markup is strict. .prs-label-text must be a direct child of .prs-input.

    <label class="prs-form-control">
      <div class-"prs-label">
        <span class="prs-label-text">Email:</span>
      </div>
      <span class="prs-input">
        <input type="text" size="8" placeholder="user" />
        <span class="prs-label-text">@</span>
        <input type="text" size="12" placeholder="domain.ext" />
      </span>
    </label>
    
    <label class="prs-form-control">
      <div class-"prs-label">
        <span class="prs-label-text">Price:</span>
      </div>
      <span class="prs-input">
        <span class="prs-label-text">$</span>
        <input type="number" min="0" max="999" step="1" pattern="[0-9]{1,3}" oninput="if(parseInt(this.value)>999){ this.value = 999; return false; }" placeholder="123" />
        <span class="prs-label-text">.00</span>
      </span>
    </label>
    
    <label class="prs-form-control">
      <div class-"prs-label">
        <span class="prs-label-text">Select:</span>
      </div>
      <span class="prs-input">
        <span class="prs-label-text">
          <!-- use <svg>, <iconify-icon>, or .icon -->
          <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" role="img" aria-label="Some label">
            <use href="/_assets/prs-icons.svg#nav-level-down" />
          </svg>
        </span>
        <select>
          <option selected disabled>Choose:</option>
          <option>1</option>
          <option>2</option>
          <option>3</option>
        </select>
      </span>
    </label>
    
  • Ghost

    Borderless variant.

    <div class="w-96 max-w-xs space-y-5 [&>:where(label:not(.prs-form-control))]:(w-full block)"> <!-- <- this is just for demo purposes -->
    
      <label><input type="text" class="prs-input prs-input-ghost" placeholder="Text" aria-label="Text" /></label>
      <label><textarea class="prs-input prs-input-ghost" placeholder="Textarea" aria-label="Textarea"></textarea></label>
      <label><select class="prs-input prs-input-ghost" aria-label="Select"><option selected disabled>Select</option></select></label>
      <label><input type="file" class="prs-input prs-input-ghost" aria-label="File" /></label>
    
    </div>
    
  • <div class="w-96 max-w-xs space-y-5 [&>:where(label:not(.prs-form-control))]:(w-full block)"> <!-- <- this is just for demo purposes -->
    
      <label><input type="email" class="prs-input" placeholder="Email" aria-label="Email" /></label>
      <label><input type="password" class="prs-input" placeholder="Password" aria-label="Password" /></label>
      <label><input type="search" class="prs-input" placeholder="Search" aria-label="Search" /></label>
      <label><input type="number" class="prs-input" placeholder="Number" aria-label="Number" /></label>
      <label><input type="tel" class="prs-input" placeholder="Phone number" aria-label="Phone number" /></label>
      <div class="grid-(& cols-5) gap-5">
        <label><input type="color" class="prs-input" placeholder="Color" aria-label="Color" value="#383838" /></label>
        <label><input type="color" class="prs-input" placeholder="Color" aria-label="Color" value="#00758E" /></label>
        <label><input type="color" class="prs-input" placeholder="Color" aria-label="Color" value="#DA1B2C" /></label>
        <label><input type="color" class="prs-input" placeholder="Color" aria-label="Color" value="#3B822E" /></label>
        <label><input type="color" class="prs-input" placeholder="Color" aria-label="Color" value="#B79E25" /></label>
      </div>
      <label><input type="url" class="prs-input" placeholder="URL" aria-label="URL" /></label>
      <label><input type="date" class="prs-input" aria-label="Date" onfocus="this.showPicker()" /></label>
      <label><input type="datetime-local" class="prs-input" aria-label="Date and time" onfocus="this.showPicker()" /></label>
      <label><input type="month" class="prs-input" aria-label="Month and year" onfocus="this.showPicker()" /></label>
      <label><input type="week" class="prs-input" aria-label="Week of the year" onfocus="this.showPicker()" /></label>
    
    </div>
    
  • <label><input type="text" class="prs-input prs-input_focus" placeholder="Placeholder" aria-label="Text" /></label>
    
  • Invalid

    .prs-label-error is hidden by default unless the .form-control tag contains an invalid input. Pure CSS.

    <div class="w-96 max-w-xs space-y-5 [&>:where(label:not(.prs-form-control))]:(w-full block)"> <!-- <- this is just for demo purposes -->
    
      <!-- native html validation - remove text then click outside -->
      <label class="prs-form-control">
        <span class="prs-label-text"><strong>Native:</strong> remove text then click outside</span>
        <input type="text" class="prs-input" placeholder="Placeholder" aria-label="Text" value="Value" onfocus="this.select()" required />
        <span class="prs-label-text prs-label-error">Error message</span>
      </label>
    
      <!--
        manually handle validation:
          - toggle .prs-input_invalid on <input/textarea/select>
          - your error message will still automatically show/hide
      -->
      <label class="prs-form-control">
        <span class="prs-label-text"><strong>Forced:</strong> with class</span>
        <input
          type="text"
          class="prs-input prs-input_invalid"
          placeholder="Placeholder"
          aria-label="Text"
          onInput={handleInput}
        />
        <span class="prs-label-text prs-label-error">Error message</span>
      </label>
    </div>
    
  • <div class="w-96 max-w-xs space-y-5 [&>:where(label:not(.prs-form-control))]:(w-full block)"> <!-- <- this is just for demo purposes -->
      <label><input type="text" class="prs-input" aria-label="Text" disabled /></label>
      <label><input type="text" class="prs-input" placeholder="Placeholder" aria-label="Text" disabled /></label>
      <label><input type="text" class="prs-input" value="Value" placeholder="Placeholder" aria-label="Text" disabled /></label>
      <label><input type="range" min="0" max="100" value="50" class="prs-input" aria-label="Range" disabled /></label>
      <label><input type="file" class="prs-input" aria-label="File" disabled /></label>
    </div>
    

CSS

Full Library

Component Only

.prs-input:where(:not([type="range"])) {
  appearance: none;
  padding: 0.1875rem 1rem;
  border: 1px solid var(--prs-c-gray-600);
  width: 100%;
  min-height: 2rem;
  background: var(--prs-c-white);
  color: var(--prs-c-gray-900);
  text-align: left;
  font: var(--prs-t-ui-md);
  flex-shrink: 1;
  display: block;
  border-radius: var(--prs-radius-input);
  transition-property: var(--prs-transition-property);
  transition-timing-function: var(--prs-transition-timing);
  transition-duration: var(--prs-transition-duration);
}

/* number */
.prs-input:where([type="number"]) {
  padding-right: 0.1875rem;
}

/* color */
.prs-input:where([type="color"]) {
  padding: 0 0.1875rem;
}

/* when used as a container */
.prs-input:where(:not(input)) {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.prs-input :where(input, select, textarea) {
  appearance: none;
  flex-grow: 1;
}
.prs-input :where(input, select, textarea):last-child,
.prs-input :where(input[type="number"]) {
  margin-inline-end: -1rem;
}
.prs-input > .prs-label-text {
  padding: 0;
  color: var(--prs-c-gray-600);
  line-height: normal;
  white-space: nowrap;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
}
.prs-input > .prs-label-text:not(:first-child,:last-child) {
  padding-inline: 1rem;
  border-inline: 1px solid var(--prs-c-gray-300);
}
.prs-input > .prs-label-text + .prs-label-text:not(:first-child,:last-child) {
  padding-inline: 0 1rem;
  border-inline-start: 0 none;
}
.prs-input > .prs-label-text:first-child {
  padding-inline-end: 1rem;
  border-inline-end: 1px solid var(--prs-c-gray-300);
}
.prs-input > .prs-label-text:last-child {
  padding-inline-start: 1rem;
  border-inline-start: 1px solid var(--prs-c-gray-300);
}
.prs-input .prs-label-text svg,.prs-input .prs-label-text .icon,.prs-input .prs-label-text iconify-icon {
  font-size: 1rem;
}

/* ghost */
.prs-input-ghost {
  border-color: transparent;
  background-color: transparent;
}

/* textarea */
.prs-input:where(textarea) {
  resize: vertical;
}

/* select */
.prs-input:where(select),.prs-input :where(select) {
  padding-right: 2rem;
  background-image: linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-size: 4px 4px, 4px 4px;
  background-position: calc(100% - 20px) calc(1px + 50%), calc(100% - 16.1px) calc(1px + 50%);
  background-repeat: no-repeat;
  cursor: pointer;
  user-select: none;
}

/* range */
.prs-input:where([type="range"]) {
  --filler: var(--prs-c-aqua);
  --filler-size: 100rem;
  --filler-offset: 0.6rem;
  appearance: none;
  width: 100%;
  height: 1rem;
  background: transparent;
  display: block;
  overflow: hidden;
  cursor: pointer;
  border-radius: 9999px;
}
.prs-input:where([type="range"])::-webkit-slider-runnable-track {
  appearance: none;
  width: 100%;
  height: 0.5rem;
  background-color: var(--prs-c-gray-300);
  align-items: center;
  border-radius: 9999px;
}
.prs-input:where([type="range"])::-moz-range-track {
  appearance: none;
  width: 100%;
  height: 0.5rem;
  background-color: var(--prs-c-gray-300);
  align-items: center;
  border-radius: 9999px;
}
.prs-input:where([type="range"])::-webkit-slider-thumb {
  appearance: none;
  border-style: none;
  width: 1rem;
  height: 1rem;
  background-color: var(--prs-c-white);
  color: var(--filler);
  position: relative;
  top: 50%;
  transform: translateY(-50%);
  border-radius: 9999px;
  box-shadow: 0 0 0 3px var(--filler) inset, 0 0, calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size);
  transition-property: var(--prs-transition-property);
  transition-timing-function: var(--prs-transition-timing);
  transition-duration: var(--prs-transition-duration);
}
.prs-input:where([type="range"])::-moz-range-thumb {
  --filler-offset: 0.5rem;
  appearance: none;
  border-style: none;
  width: 1rem;
  height: 1rem;
  background-color: var(--prs-c-white);
  color: var(--filler);
  position: relative;
  top: 50%;
  border-radius: 9999px;
  box-shadow: 0 0 0 3px var(--filler) inset, 0 0, calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size);
  transition-property: var(--prs-transition-property);
  transition-timing-function: var(--prs-transition-timing);
  transition-duration: var(--prs-transition-duration);
}
.prs-input:not([disabled]):where([type="range"]):hover::-webkit-slider-thumb {
  --filler: var(--prs-c-aqua-600);
}
.prs-input:not([disabled]):where([type="range"]):hover::-moz-range-thumb {
  --filler: var(--prs-c-aqua-600);
}
.prs-input:not([disabled]):where([type="range"]):focus-visible {
  outline: 2px solid var(--filler);
  outline-offset: 1px;
}

/* file */
.prs-input:where([type="file"]) {
  padding: 0;
  padding-right: 1rem;
}
.prs-input::file-selector-button {
  appearance: none;
  padding: .25rem 1rem;
  border-width: var(--prs-border-btn);
  border-style: solid;
  border-color: transparent;
  font-size: 1rem;
  font-style: normal;
  line-height: 1.375rem;
  background-color: var(--prs-c-aqua);
  color: var(--prs-c-white);
  text-align: center;
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  cursor: pointer;
  user-select: none;
  border-radius: inherit;
  transition-property: var(--prs-transition-property);
  transition-timing-function: var(--prs-transition-timing);
  transition-duration: var(--prs-transition-duration);
}
.prs-input:hover::file-selector-button {
  background-color: var(--prs-c-aqua-600);
}

/* placeholder */
.prs-input::placeholder,.prs-input ::placeholder {
  color: var(--prs-c-gray-600);
  font-style: italic;
}

/* focus */
.prs-input:where(:not([type="range"])):focus,.prs-input:where(:not([type="range"])):focus-within,.prs-input_focus:where(:not([type="range"])) {
  outline: 2px solid var(--prs-c-aqua);
  outline-offset: 1px;
}
.prs-input :focus {
  outline: 0 none;
}

/* invalid */
.prs-input:user-invalid,.prs-input:has(:user-invalid),.prs-input_invalid {
  border-color: var(--prs-c-red);
  background-color: var(--prs-c-red-200);
}

/* disabled */
.prs-input:disabled,.prs-input_disabled {
  border-color: var(--prs-c-gray);
  color: var(--prs-c-gray-600);
  background-color: var(--prs-c-gray-200);
  cursor: not-allowed;
}
.prs-input:disabled::placeholder,.prs-input_disabled::placeholder {
  color: var(--prs-c-gray-400);
}
.prs-input:where([type="range"]):disabled {
  --filler: var(--prs-c-gray-400);
  background-color: transparent;
  cursor: not-allowed;
}
.prs-input:disabled::file-selector-button,
.prs-input_disabled::file-selector-button {
  background-color: var(--prs-c-gray-400);
  color: white;
  cursor: not-allowed;
}

Figma

Coming soon...