Inputs

Settings Switch List

A settings section: rows with switches, hairline dividers and a textual On/Off status to the right of every caption.

  • switch
  • settings
  • list
  • fieldset

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/switch-003?lang=en

Notifications
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 "switch-003" (Settings Switch List) 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/switch-003.json

Registry item: https://vibeui.ru/r/switch-003.json
Installs to: components/vibeui/switch-003.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 settings section: rows with switches, hairline dividers and a textual On/Off status to the right of every caption.

A settings section listing switch rows with a textual status on each row. One file, zero dependencies, a server component.

## 3. How to use it
import { Switch003 } from "@/components/vibeui/switch-003"

<Switch003
  legend="Notifications"
/>

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-switch-003-* palette — do not swap it for your theme tokens
- the fieldset with a legend: without them the rows are not announced as one settings section
- float:left on the legend together with clear:both on the list — otherwise the legend sits on the border and rows wrap around it
- the textual On/Off status: the thumb colour alone does not say what is enabled
- native checkboxes with role="switch" in every row: keyboard and form support come for free
- the component's own light surface — without it the dark text disappears on a dark page

## 6. You may change
- the section title through legend
- the rows, captions and initial states through items
- the accent colour through accent
- the card width through max-width in the block CSS

## 7. Rules
- The status is drawn with CSS content — when localising the UI those two strings live in the styles, not the markup.
- Every switch takes its name from items: without it the form submits a single field for the whole group.
- The divider is a shadow, so the first row has no line above it — that is intentional.
- 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/switch-003.json
https://vibeui.ru/r/switch-003.json

Серверный компонент на fieldset с legend: скринридер объявляет название раздела перед строками. Легенда прижата float:left, а список получает clear:both — иначе следующие блоки обтекают её. Статус подставляется псевдоэлементом ::after, а переключается правилом :has(input:checked) в границах строки, поэтому JS не нужен. Разделители — inset box-shadow на соседних строках, чтобы не заводить лишние узлы. Палитра в --vibeui-switch-003-*.

Component source

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

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Switch003Item = {
  id: string
  label: string
  hint?: string
  defaultChecked?: boolean
}

export type Switch003Props = Omit<
  ComponentPropsWithoutRef<"fieldset">,
  "children"
> & {
  legend?: string
  items?: Switch003Item[]
  accent?: string
}

// Идея компонента: не один переключатель, а раздел настроек. Строки идут
// сплошным списком с тонкими разделителями, поэтому глаз читает их как
// таблицу, а не как набор карточек; статус «Вкл/Выкл» подписан текстом,
// потому что цвет бегунка сам по себе не говорит, что сейчас включено.
const STYLES = `
:where([data-vibeui-block="switch-003"]){
--vibeui-switch-003-bg:oklch(1 0 0);
--vibeui-switch-003-fg:oklch(0.22 0.014 265);
--vibeui-switch-003-muted:oklch(0.55 0.014 265);
--vibeui-switch-003-border:oklch(0.91 0.006 265);
--vibeui-switch-003-track:oklch(0.88 0.008 265);
--vibeui-switch-003-thumb:oklch(1 0 0);
--vibeui-switch-003-accent:oklch(0.55 0.19 262);
--vibeui-switch-003-hover:oklch(0.55 0.02 265 / 6%);
--vibeui-switch-003-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="switch-003"]{
width:100%;max-width:23rem;box-sizing:border-box;
margin:0;padding:0.5rem 0.875rem 0.875rem;
background:var(--vibeui-switch-003-bg);
border:1px solid var(--vibeui-switch-003-border);border-radius:0.875rem;
font-family:var(--vibeui-switch-003-font);color:var(--vibeui-switch-003-fg);
}
/* float + clear: без него легенда садится на рамку fieldset, а следующие
   строки начинают обтекать её вместо нормального потока. */
[data-vibeui-block="switch-003"] legend{
float:left;width:100%;padding:0;margin:0.375rem 0 0.5rem;
font-size:0.6875rem;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
color:var(--vibeui-switch-003-muted);
}
[data-vibeui-block="switch-003"] [data-part="list"]{clear:both;display:flex;flex-direction:column}
[data-vibeui-block="switch-003"] [data-part="row"]{
display:flex;align-items:center;gap:0.875rem;
padding:0.625rem 0.5rem;margin:0 -0.5rem;border-radius:0.5rem;cursor:pointer;
transition:background-color .16s ease;
}
[data-vibeui-block="switch-003"] [data-part="row"] + [data-part="row"]{
box-shadow:inset 0 1px 0 var(--vibeui-switch-003-border);
}
[data-vibeui-block="switch-003"] [data-part="row"]:hover{background:var(--vibeui-switch-003-hover)}
[data-vibeui-block="switch-003"] [data-part="text"]{display:flex;flex-direction:column;gap:0.125rem;flex:1 1 auto;min-width:0}
[data-vibeui-block="switch-003"] [data-part="label"]{font-size:0.875rem;line-height:1.3}
[data-vibeui-block="switch-003"] [data-part="hint"]{font-size:0.75rem;line-height:1.4;color:var(--vibeui-switch-003-muted)}
[data-vibeui-block="switch-003"] [data-part="state"]{
flex:none;min-width:2.25rem;text-align:right;
font-size:0.6875rem;font-weight:600;color:var(--vibeui-switch-003-muted);
}
/* Текстовый статус переключается на CSS: :has() смотрит на состояние
   input'а в той же строке. */
[data-vibeui-block="switch-003"] [data-part="state"]::after{content:"Выкл"}
[data-vibeui-block="switch-003"] [data-part="row"]:has(input:checked) [data-part="state"]{color:var(--vibeui-switch-003-accent)}
[data-vibeui-block="switch-003"] [data-part="row"]:has(input:checked) [data-part="state"]::after{content:"Вкл"}
[data-vibeui-block="switch-003"] [data-part="track"]{position:relative;display:flex;flex:none}
[data-vibeui-block="switch-003"] input{
appearance:none;-webkit-appearance:none;margin:0;
width:2.375rem;height:1.375rem;border-radius:9999px;
background:var(--vibeui-switch-003-track);cursor:inherit;
transition:background-color .18s ease;
}
[data-vibeui-block="switch-003"] input:checked{background:var(--vibeui-switch-003-accent)}
[data-vibeui-block="switch-003"] input:focus-visible{outline:2px solid var(--vibeui-switch-003-accent);outline-offset:2px}
[data-vibeui-block="switch-003"] [data-part="thumb"]{
position:absolute;left:0.1875rem;top:0.1875rem;
width:1rem;height:1rem;border-radius:9999px;pointer-events:none;
background:var(--vibeui-switch-003-thumb);
box-shadow:0 1px 2px oklch(0.2 0.02 265 / 28%);
transition:transform .18s cubic-bezier(.32,.72,0,1);
}
[data-vibeui-block="switch-003"] input:checked + [data-part="thumb"]{transform:translateX(1rem)}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="switch-003"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_ITEMS: Switch003Item[] = [
  {
    id: "digest",
    label: "Еженедельный дайджест",
    hint: "Письмо по понедельникам утром.",
    defaultChecked: true,
  },
  {
    id: "mentions",
    label: "Упоминания в комментариях",
    hint: "Уведомление, когда вас позвали в обсуждение.",
    defaultChecked: true,
  },
  {
    id: "marketing",
    label: "Новости продукта",
    hint: "Релизы и большие обновления, не чаще раза в месяц.",
  },
  {
    id: "sms",
    label: "SMS о списаниях",
    hint: "Платные — по тарифу оператора.",
  },
]

/**
 * Раздел настроек: список строк с переключателями и текстовым статусом.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Switch003({
  legend = "Уведомления",
  items = DEFAULT_ITEMS,
  accent,
  className,
  style,
  ...props
}: Switch003Props) {
  const palette = {
    ...(accent ? { "--vibeui-switch-003-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-switch-003" precedence="medium">
        {STYLES}
      </style>
      <fieldset
        {...props}
        data-vibeui-block="switch-003"
        className={className}
        style={palette}
      >
        <legend>{legend}</legend>
        <div data-part="list">
          {items.map((item) => (
            <label key={item.id} data-part="row">
              <span data-part="text">
                <span data-part="label">{item.label}</span>
                {item.hint ? <span data-part="hint">{item.hint}</span> : null}
              </span>
              <span data-part="state" aria-hidden="true" />
              <span data-part="track">
                <input
                  type="checkbox"
                  role="switch"
                  name={item.id}
                  defaultChecked={item.defaultChecked}
                />
                <span data-part="thumb" aria-hidden="true" />
              </span>
            </label>
          ))}
        </div>
      </fieldset>
    </>
  )
}