Label

Locked Reason

A label for a locked field with the reason and a way out: the input is readonly rather than disabled, so it stays reachable from the keyboard.

  • label
  • disabled
  • readonly
  • a11y

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/label-008?lang=en

The plan can only change after the current period is paid — it closes on 30 September. Pay now

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 "label-008" (Locked Reason) 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/label-008.json

Registry item: https://vibeui.ru/r/label-008.json
Installs to: components/vibeui/label-008.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 label for a locked field with the reason and a way out: the input is readonly rather than disabled, so it stays reachable from the keyboard.

A locked-field label: a muted label with a padlock, the reason spelled out and a link that lifts the lock. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Label008 } from "@/components/vibeui/label-008"

<Label008
  label="Plan"
  reason="The plan changes once the current period is paid."
  actionText="Pay now"
  actionHref="/billing"
/>

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
- the local --vibeui-label-008-* palette — do not swap it for your theme tokens
- readOnly with aria-disabled instead of disabled: a real disabled throws the field — and the reason with it — out of tab order
- the aria-describedby link between the reason and the field — otherwise the explanation is visible only to the eye
- the visible focus ring: a locked field still receives focus and has to show it
- the way-out link next to the reason: a lock with no way to lift it is a dead end
- the block's own light surface: without it the dark text disappears on a dark background

## 6. You may change
- the field name through label, the explanation through reason, the link caption through actionText and its address through actionHref
- the focus and link color through accent
- the locked field background through the --vibeui-label-008-locked-bg variable
- the value inside the field through defaultValue

## 7. Rules
- A readonly field is submitted with the form while a disabled one is not: drop its name if the value should not be sent.
- The reason has to name the condition and the date rather than say "unavailable", or people go and write to support.
- Muted text still has to pass contrast: do not push it below 4.5:1 against the surface.
- 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/label-008.json
https://vibeui.ru/r/label-008.json

Компонент самодостаточен: один файл, без зависимостей, палитра в локальных переменных --vibeui-label-008-*. Серверный: из хуков только useId, состояния нет. Ключевое решение — readOnly плюс aria-disabled вместо настоящего disabled: disabled выбрасывает поле из порядка обхода, и человек с клавиатуры до причины не доберётся, а readonly оставляет фокус и позволяет прочитать привязанный через aria-describedby текст. Замок нарисован средствами CSS: корпус — фон элемента, дужка — рамка псевдоэлемента, иконочный пакет ради одного значка не нужен. Рядом с причиной стоит ссылка, снимающая запрет, — без неё интерфейс превращается в тупик.

Component source

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

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

export type Label008Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "children"
> & {
  label?: string
  reason?: string
  actionText?: string
  actionHref?: string
  accent?: string
}

// Идея компонента: выключенное поле без объяснения читается как поломка.
// Поле здесь не disabled, а readonly с aria-disabled: так оно остаётся в
// порядке обхода, и скринридер успевает прочитать причину, привязанную
// через aria-describedby. Рядом стоит ссылка, снимающая запрет, — иначе
// тупик.
const STYLES = `
:where([data-vibeui-block="label-008"]){
--vibeui-label-008-surface:oklch(1 0 0);
--vibeui-label-008-surface-border:oklch(0.91 0.006 265);
--vibeui-label-008-fg:oklch(0.24 0.016 265);
--vibeui-label-008-muted:oklch(0.54 0.014 265);
--vibeui-label-008-locked-bg:oklch(0.97 0.003 265);
--vibeui-label-008-locked-border:oklch(0.89 0.008 265);
--vibeui-label-008-accent:oklch(0.55 0.2 262);
--vibeui-label-008-radius:0.625rem;
--vibeui-label-008-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="label-008"]{
box-sizing:border-box;width:100%;max-width:24rem;
padding:1rem;border-radius:0.875rem;
background:var(--vibeui-label-008-surface);
border:1px solid var(--vibeui-label-008-surface-border);
font-family:var(--vibeui-label-008-font);color:var(--vibeui-label-008-fg);
display:flex;flex-direction:column;gap:0.375rem;
}
[data-vibeui-block="label-008"] [data-part="row"]{
display:flex;align-items:center;gap:0.375rem;
}
[data-vibeui-block="label-008"] label{
font-size:0.875rem;font-weight:600;line-height:1.3;
color:var(--vibeui-label-008-muted);
}
/* Замок нарисован двумя прямоугольниками: корпус — фон элемента, дужка —
   рамка псевдоэлемента. Иконочный пакет ради одного значка не нужен. */
[data-vibeui-block="label-008"] [data-part="lock"]{
position:relative;flex:none;
width:0.625rem;height:0.5rem;margin-top:0.1875rem;border-radius:0.125rem;
background:var(--vibeui-label-008-muted);
}
[data-vibeui-block="label-008"] [data-part="lock"]::before{
content:"";position:absolute;left:50%;top:-0.3125rem;translate:-50% 0;
width:0.375rem;height:0.3125rem;
border:1.5px solid var(--vibeui-label-008-muted);border-bottom:0;
border-radius:0.25rem 0.25rem 0 0;
}
[data-vibeui-block="label-008"] input{
box-sizing:border-box;width:100%;height:2.5rem;padding:0 0.75rem;
font:inherit;font-size:0.9375rem;
color:var(--vibeui-label-008-muted);
background:var(--vibeui-label-008-locked-bg);
border:1px solid var(--vibeui-label-008-locked-border);
border-radius:var(--vibeui-label-008-radius);
cursor:not-allowed;
}
[data-vibeui-block="label-008"] input:focus-visible{
outline:2px solid var(--vibeui-label-008-accent);outline-offset:2px;
}
[data-vibeui-block="label-008"] [data-part="reason"]{
margin:0;font-size:0.8125rem;line-height:1.45;
color:var(--vibeui-label-008-muted);
}
[data-vibeui-block="label-008"] a{
color:var(--vibeui-label-008-accent);font-weight:500;
text-underline-offset:0.15em;
}
[data-vibeui-block="label-008"] a:focus-visible{
outline:2px solid var(--vibeui-label-008-accent);outline-offset:2px;
border-radius:0.25rem;
}
`

/**
 * Подпись выключенного поля с причиной и выходом: поле readonly, а не
 * disabled, поэтому остаётся доступным с клавиатуры. Один файл, ноль
 * зависимостей.
 */
export function Label008({
  label = "Тариф",
  reason = "Тариф меняется только после оплаты текущего периода — он закрывается 30 сентября.",
  actionText = "Оплатить сейчас",
  actionHref = "#",
  accent,
  className,
  style,
  ...props
}: Label008Props) {
  const id = useId()
  const reasonId = `${id}-reason`
  const palette = {
    ...(accent ? { "--vibeui-label-008-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-label-008" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="label-008"
        className={className}
        style={palette}
      >
        <div data-part="row">
          <span data-part="lock" aria-hidden="true" />
          <label htmlFor={id}>{label}</label>
        </div>
        <input
          id={id}
          type="text"
          name="plan"
          defaultValue="Команда, 12 мест"
          readOnly
          aria-disabled="true"
          aria-describedby={reasonId}
        />
        <p data-part="reason" id={reasonId}>
          {reason} <a href={actionHref}>{actionText}</a>
        </p>
      </div>
    </>
  )
}