Buttons

Reveal Password Button

A reveal-password button inside the field: it flips only the type, so the entered text and the caret position stay put.

  • input
  • password
  • toggle
  • form

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/button-058?lang=en

Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "button-058" (Reveal Password Button) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/button-058.json

Registry item: https://vibeui.ru/r/button-058.json
Installs to: components/vibeui/button-058.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
A reveal-password button inside the field: it flips only the type, so the entered text and the caret position stay put.

A password field with an eye button at its right edge. Pressing it flips only the field's type, so the text and the caret survive; while hidden the eye is crossed by a line with a cut-out in the surface colour. The state is announced through aria-pressed and the button's name switches between show and hide. Zero dependencies, one file.

## 3. How to use it
import { Button058 } from "@/components/vibeui/button-058"

<Button058 label="Password" placeholder="Enter password" />

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- flipping only the type attribute: recreating the field resets the caret and breaks password managers
- aria-pressed together with the aria-label swap — a single icon does not convey the state
- autoComplete="current-password": without it browsers and password managers do not recognise the field
- the focus ring on the wrapper via :has(input:focus-visible) — focus lands on the input, but the ring belongs to the whole field
- the slash with a cut-out in the surface colour: a solid line over the eye reads worse
- the button's position inside the field and the input's right padding: the text must not run under the button

## 6. You may change
- the field label through the label prop
- the hint text through the placeholder prop
- the initial value through the defaultValue prop
- the accent colour through the accent prop
- the button names through the showLabel and hideLabel props

## 7. Rules
- The field id is fixed inside the component: if the page has several fields, make it a prop.
- Do not leave the password revealed by default — revealing has to be a deliberate action.
- Do not log or submit the field's value from the component: it knows nothing about your form.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/button-058.json
https://vibeui.ru/r/button-058.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-button-058-*. Клиентский компонент: состояние показа внутреннее. Поле всегда одно и то же — меняется только атрибут type, поэтому React не пересоздаёт узел и каретка не сбрасывается. Состояние объявлено через aria-pressed, а имя кнопки меняется вместе с ним. Обводку фокуса рисует обёртка поля через :has(input:focus-visible). Перечёркивание глаза — отдельный узел с тенью цвета подложки, дающей вырез.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

"use client"

import { useState } from "react"
import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Button058Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "children"
> & {
  /** Подпись поля: без неё кнопка-глаз висит в воздухе. */
  label?: string
  placeholder?: string
  defaultValue?: string
  showLabel?: string
  hideLabel?: string
  accent?: string
}

// Идея компонента: кнопка «показать пароль» живёт внутри поля. Состояние
// объявлено через aria-pressed, а не сменой иконки, и подпись кнопки меняется
// вместе с ним. Поле всегда одно и то же: меняется только type, поэтому
// каретка и введённый текст на месте. autoComplete остаётся current-password.
const STYLES = `
:where([data-vibeui-block="button-058"]){
--vibeui-button-058-surface:oklch(1 0 0);
--vibeui-button-058-border:oklch(0.88 0.006 265);
--vibeui-button-058-fg:oklch(0.24 0.02 265);
--vibeui-button-058-muted:oklch(0.56 0.014 265);
--vibeui-button-058-accent:oklch(0.53 0.16 265);
--vibeui-button-058-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="button-058"]{
display:flex;flex-direction:column;gap:0.375rem;box-sizing:border-box;
width:100%;max-width:20rem;
font-family:var(--vibeui-button-058-font);color:var(--vibeui-button-058-fg);
}
[data-vibeui-block="button-058"] label{font-size:0.75rem;font-weight:600;color:var(--vibeui-button-058-muted)}
[data-vibeui-block="button-058"] [data-part="field"]{
position:relative;display:flex;align-items:center;
border:1px solid var(--vibeui-button-058-border);border-radius:0.625rem;
background:var(--vibeui-button-058-surface);
transition:border-color .16s ease;
}
[data-vibeui-block="button-058"] [data-part="field"]:has(input:focus-visible){
border-color:var(--vibeui-button-058-accent);
outline:2px solid color-mix(in oklab,var(--vibeui-button-058-accent) 45%,transparent);
outline-offset:1px;
}
[data-vibeui-block="button-058"] input{
flex:1;min-width:0;appearance:none;border:0;background:transparent;outline:0;
height:2.625rem;padding:0 2.75rem 0 0.75rem;
font:inherit;font-size:0.9375rem;color:inherit;
letter-spacing:0.02em;
}
[data-vibeui-block="button-058"] [data-part="toggle"]{
position:absolute;right:0.3125rem;
appearance:none;border:0;background:transparent;cursor:pointer;
display:inline-flex;align-items:center;justify-content:center;
width:2rem;height:2rem;border-radius:0.4375rem;color:var(--vibeui-button-058-muted);
transition:background-color .16s ease,color .16s ease;
}
[data-vibeui-block="button-058"] [data-part="toggle"]:hover{
background:color-mix(in oklab,var(--vibeui-button-058-accent) 10%,transparent);
color:var(--vibeui-button-058-accent);
}
[data-vibeui-block="button-058"] [data-part="toggle"]:focus-visible{outline:2px solid var(--vibeui-button-058-accent);outline-offset:2px}
[data-vibeui-block="button-058"] [data-part="toggle"][aria-pressed="true"]{color:var(--vibeui-button-058-accent)}
[data-vibeui-block="button-058"] [data-part="eye"]{position:relative;width:1.125rem;height:1.125rem}
[data-vibeui-block="button-058"] [data-part="eye"]::before{
content:"";position:absolute;left:0;top:50%;width:1.125rem;height:0.75rem;
margin-top:-0.375rem;box-sizing:border-box;
border:1.5px solid currentColor;border-radius:0.5625rem;
}
[data-vibeui-block="button-058"] [data-part="eye"]::after{
content:"";position:absolute;left:50%;top:50%;width:0.3125rem;height:0.3125rem;
margin:-0.15625rem 0 0 -0.15625rem;border-radius:50%;background:currentColor;
}
/* Перечёркнутый глаз — состояние «скрыто». */
[data-vibeui-block="button-058"] [data-part="slash"]{
position:absolute;left:-0.0625rem;top:50%;width:1.25rem;height:1.5px;
background:currentColor;transform:rotate(-40deg);transform-origin:center;
box-shadow:0 -2px 0 0 var(--vibeui-button-058-surface);
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="button-058"] *{animation:none!important;transition:none!important}}
`

/**
 * Поле пароля с кнопкой «показать»: состояние объявлено через aria-pressed.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Button058({
  label = "Пароль",
  placeholder = "Введите пароль",
  defaultValue = "correct-horse-battery",
  showLabel = "Показать пароль",
  hideLabel = "Скрыть пароль",
  accent,
  className,
  style,
  ...props
}: Button058Props) {
  const [shown, setShown] = useState(false)

  const palette = {
    ...(accent ? { "--vibeui-button-058-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-button-058" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="button-058"
        className={className}
        style={palette}
      >
        <label htmlFor="vibeui-button-058-input">{label}</label>
        <div data-part="field">
          <input
            id="vibeui-button-058-input"
            type={shown ? "text" : "password"}
            defaultValue={defaultValue}
            placeholder={placeholder}
            autoComplete="current-password"
          />
          <button
            type="button"
            data-part="toggle"
            aria-pressed={shown}
            aria-label={shown ? hideLabel : showLabel}
            title={shown ? hideLabel : showLabel}
            onClick={() => setShown((value) => !value)}
          >
            <span data-part="eye" aria-hidden="true">
              {shown ? null : <span data-part="slash" />}
            </span>
          </button>
        </div>
      </div>
    </>
  )
}