Toggle

Grid Overlay

A "show grid" toggle: the button is tied to the canvas through aria-controls, and the grid switches on as a separate layer above the layout without moving anything.

  • toggle
  • grid
  • aria-controls
  • canvas

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/toggle-004?lang=en

Макет карточки

Шаг сетки — 20 px.

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 "toggle-004" (Grid Overlay) 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/toggle-004.json

Registry item: https://vibeui.ru/r/toggle-004.json
Installs to: components/vibeui/toggle-004.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 "show grid" toggle: the button is tied to the canvas through aria-controls, and the grid switches on as a separate layer above the layout without moving anything.

A toggle button that turns on a grid over the layout: the link to the region is declared with aria-controls and the cell step comes from a prop. One file, zero dependencies.

## 3. How to use it
import { Toggle004 } from "@/components/vibeui/toggle-004"

<Toggle004
  label="Show grid"
  step={20}
  defaultPressed
/>

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-toggle-004-* palette — do not swap it for your theme tokens
- aria-controls on the canvas: otherwise the link between button and region exists only visually
- the grid as a separate layer with pointer-events:none — otherwise it swallows clicks on the layout
- switching by opacity rather than rebuilding the layout: content must not jump
- aria-hidden on the grid layer: for a screen reader it is decoration
- the panel's own light surface: dark text must bring its own background

## 6. You may change
- the button caption through label
- the grid step through step
- the initial state through defaultPressed
- the line and button colour through accent

## 7. Rules
- step is in pixels: too small a step turns the grid into a grey wash.
- The grid layer does not print well over content — switch it off for printing.
- This is a view, not a setting: if the grid must survive a reload, keep the state outside.
- 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/toggle-004.json
https://vibeui.ru/r/toggle-004.json

Один файл, ноль зависимостей, палитра --vibeui-toggle-004-*. Клиентский: useState и useId. Кнопка объявляет aria-controls на холст — так связь «кнопка → область» есть не только в голове автора. Сетка нарисована двумя linear-gradient на отдельном слое с pointer-events:none и включается прозрачностью, поэтому макет под ней не перерисовывается и не дёргается. Шаг клетки приходит пропом step и уезжает в CSS-переменную --vibeui-toggle-004-step инлайном — это динамическое значение, классом его не выразить. Toggle, а не switch: вид меняется здесь и сейчас.

Component source

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

"use client"

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

export type Toggle004Props = Omit<
  ComponentPropsWithoutRef<"section">,
  "children" | "onChange"
> & {
  label?: string
  step?: number
  defaultPressed?: boolean
  onChange?: (pressed: boolean) => void
  accent?: string
}

// Идея компонента: toggle, который управляет соседней областью и говорит об
// этом разметкой. aria-controls связывает кнопку с холстом, поэтому «Показать
// сетку» — не абстрактная настройка, а видимое действие над конкретным видом.
const STYLES = `
:where([data-vibeui-block="toggle-004"]){
--vibeui-toggle-004-bg:oklch(1 0 0);
--vibeui-toggle-004-fg:oklch(0.23 0.014 265);
--vibeui-toggle-004-muted:oklch(0.55 0.014 265);
--vibeui-toggle-004-border:oklch(0.9 0.006 265);
--vibeui-toggle-004-canvas:oklch(0.975 0.004 265);
--vibeui-toggle-004-accent:oklch(0.58 0.15 200);
--vibeui-toggle-004-line:oklch(0.58 0.15 200 / 26%);
--vibeui-toggle-004-step:1.25rem;
--vibeui-toggle-004-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="toggle-004"]{
box-sizing:border-box;display:flex;flex-direction:column;gap:0.625rem;
width:100%;max-width:24rem;padding:0.875rem;
border:1px solid var(--vibeui-toggle-004-border);border-radius:0.875rem;
background:var(--vibeui-toggle-004-bg);color:var(--vibeui-toggle-004-fg);
font-family:var(--vibeui-toggle-004-font);
}
[data-vibeui-block="toggle-004"] *{box-sizing:border-box}
[data-vibeui-block="toggle-004"] [data-part="bar"]{
display:flex;align-items:center;justify-content:space-between;gap:0.75rem;
}
[data-vibeui-block="toggle-004"] [data-part="caption"]{
margin:0;font-size:0.8125rem;font-weight:600;
}
[data-vibeui-block="toggle-004"] button{
appearance:none;cursor:pointer;font:inherit;
display:inline-flex;align-items:center;gap:0.4375rem;
height:2rem;padding:0 0.75rem;
border:1px solid var(--vibeui-toggle-004-border);border-radius:0.5rem;
background:var(--vibeui-toggle-004-bg);color:var(--vibeui-toggle-004-muted);
font-size:0.8125rem;font-weight:600;line-height:1;
transition:background-color .16s ease,color .16s ease,border-color .16s ease;
}
[data-vibeui-block="toggle-004"] button svg{width:0.9375rem;height:0.9375rem}
[data-vibeui-block="toggle-004"] button:focus-visible{
outline:2px solid var(--vibeui-toggle-004-accent);outline-offset:2px;
}
[data-vibeui-block="toggle-004"] button[aria-pressed="true"]{
border-color:var(--vibeui-toggle-004-accent);color:var(--vibeui-toggle-004-accent);
background:color-mix(in oklab,var(--vibeui-toggle-004-accent) 10%,white);
}
[data-vibeui-block="toggle-004"] [data-part="canvas"]{
position:relative;height:8.5rem;border-radius:0.625rem;overflow:hidden;
border:1px solid var(--vibeui-toggle-004-border);
background:var(--vibeui-toggle-004-canvas);
}
/* Сетка рисуется отдельным слоем поверх содержимого: так она включается
   без перерисовки самого макета и ничего в нём не двигает. */
[data-vibeui-block="toggle-004"] [data-part="grid"]{
position:absolute;inset:0;pointer-events:none;opacity:0;
background-image:linear-gradient(to right,var(--vibeui-toggle-004-line) 1px,transparent 1px),linear-gradient(to bottom,var(--vibeui-toggle-004-line) 1px,transparent 1px);
background-size:var(--vibeui-toggle-004-step) var(--vibeui-toggle-004-step);
transition:opacity .18s ease;
}
[data-vibeui-block="toggle-004"][data-grid="true"] [data-part="grid"]{opacity:1}
[data-vibeui-block="toggle-004"] [data-part="art"]{
position:absolute;inset:0;padding:1rem;display:flex;flex-direction:column;gap:0.5rem;
}
[data-vibeui-block="toggle-004"] [data-part="art"] span{
display:block;border-radius:0.375rem;background:oklch(0.86 0.02 265);
}
[data-vibeui-block="toggle-004"] [data-part="art"] span:nth-child(1){height:1.25rem;width:58%}
[data-vibeui-block="toggle-004"] [data-part="art"] span:nth-child(2){height:0.5rem;width:84%}
[data-vibeui-block="toggle-004"] [data-part="art"] span:nth-child(3){height:0.5rem;width:72%}
[data-vibeui-block="toggle-004"] [data-part="art"] span:nth-child(4){height:2rem;width:38%;margin-top:auto;background:var(--vibeui-toggle-004-accent)}
[data-vibeui-block="toggle-004"] [data-part="note"]{
margin:0;font-size:0.75rem;color:var(--vibeui-toggle-004-muted);
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="toggle-004"] *{animation:none!important;transition:none!important}}
`

/**
 * Toggle «показать сетку»: кнопка связана с холстом через aria-controls,
 * сетка включается слоем поверх макета. Один файл, ноль зависимостей.
 */
export function Toggle004({
  label = "Показать сетку",
  step = 20,
  defaultPressed = true,
  onChange,
  accent,
  className,
  style,
  ...props
}: Toggle004Props) {
  const [pressed, setPressed] = useState(defaultPressed)
  const canvasId = useId()

  const palette = {
    "--vibeui-toggle-004-step": `${step}px`,
    ...(accent ? { "--vibeui-toggle-004-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-toggle-004" precedence="medium">
        {STYLES}
      </style>
      <section
        {...props}
        data-vibeui-block="toggle-004"
        data-grid={pressed}
        className={className}
        style={palette}
      >
        <div data-part="bar">
          <p data-part="caption">Макет карточки</p>
          <button
            type="button"
            aria-pressed={pressed}
            aria-controls={canvasId}
            onClick={() => {
              setPressed(!pressed)
              onChange?.(!pressed)
            }}
          >
            <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
              <path
                d="M1.5 6h13M1.5 10h13M6 1.5v13M10 1.5v13"
                stroke="currentColor"
                strokeWidth="1.3"
                strokeLinecap="round"
              />
            </svg>
            {label}
          </button>
        </div>
        <div data-part="canvas" id={canvasId}>
          <div data-part="art">
            <span />
            <span />
            <span />
            <span />
          </div>
          <div data-part="grid" aria-hidden="true" />
        </div>
        <p data-part="note">Шаг сетки — {step} px.</p>
      </section>
    </>
  )
}