# Popover Guides

## Patterns

### Quick Actions

When to use:

- Several small actions should stay attached to one trigger.

Composition notes:

- Keep the content compact.
- Prefer action lists or short grouped commands over paragraph-heavy content.
- Control `open` and close the popover when an action runs; it does not close on its own.

Trade-offs:

- Fast contextual access.
- Easy to overload with too many actions.

```tsx
import { useState } from 'react';
import {
  Button,
  Popover,
  PopoverContent,
  PopoverTrigger,
  Text,
} from '@vega-ui/react';

const actions = ['Duplicate', 'Move to folder', 'Download as PDF', 'Archive'];

export default function MenuClosesOnSelect() {
  const [open, setOpen] = useState(false);
  const [last, setLast] = useState('');

  const run = (action: string) => {
    setLast(action);
    setOpen(false);
  };

  return (
    <div style={{ display: 'grid', justifyItems: 'center', gap: 'var(--spacing-6)', width: '100%' }}>
      <Popover placement='bottom-start' open={open} onChangeOpen={setOpen}>
        <PopoverTrigger asChild>
          <Button variant='secondary' appearance='ghost'>Brand guidelines.fig</Button>
        </PopoverTrigger>
        <PopoverContent style={{ padding: 'var(--spacing-2)' }}>
          <div role='group' aria-label='File actions' style={{ display: 'grid', minWidth: 200 }}>
            {actions.map((action) => (
              <Button
                key={action}
                variant='secondary'
                appearance='transparent'
                style={{ justifyContent: 'flex-start' }}
                onClick={() => run(action)}
              >
                {action}
              </Button>
            ))}
          </div>
        </PopoverContent>
      </Popover>
      <Text size={2} style={{ minHeight: '1lh', color: 'var(--label-secondary)' }}>{last ? `Last action: ${last}` : ''}</Text>
    </div>
  );
}
```

### Inline Rename Flow

When to use:

- A tiny form should stay near its trigger.

Composition notes:

- Keep validation and submit behavior simple.

Trade-offs:

- Efficient for small edits.
- Too small for longer form flows.

```tsx
import { useId, useState, type CSSProperties, type FormEvent } from 'react';
import {
  Button,
  Heading,
  Icon,
  IconButton,
  Label,
  Popover,
  PopoverContent,
  PopoverTrigger,
  TextField,
  TextFieldInput,
} from '@vega-ui/react';
import { XIcon } from '@vega-ui/icons';

export default function InlineRenameFlow() {
  const titleId = useId();
  const fieldId = useId();
  const [open, setOpen] = useState(false);
  const [name, setName] = useState('Q4 launch plan');

  const onSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setName(String(new FormData(event.currentTarget).get('name')).trim());
    setOpen(false);
  };

  return (
    <Popover role='dialog' placement='bottom-start' open={open} onChangeOpen={setOpen}>
      <PopoverTrigger asChild>
        <Button variant='secondary' appearance='ghost'>{name}</Button>
      </PopoverTrigger>
      <PopoverContent aria-labelledby={titleId}>
        <form onSubmit={onSubmit} style={{ display: 'grid', width: 260 }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--spacing-8)' }}>
            <Heading id={titleId} as='h3' size={5} fontWeight={600}>Rename document</Heading>
            <IconButton
              aria-label='Close'
              variant='secondary'
              appearance='ghost'
              style={{ flexShrink: 0, '--icon-button-size': '24px', '--icon-button-br-ratio': 0.5 } as CSSProperties}
              onClick={() => setOpen(false)}
            >
              <Icon size='xs'><XIcon strokeWidth={2.5} /></Icon>
            </IconButton>
          </div>
          <div style={{ display: 'grid', gap: 'var(--spacing-3)', marginBlockStart: 'var(--spacing-6)' }}>
            <Label htmlFor={fieldId}>Name</Label>
            <TextField size='sm'>
              <TextFieldInput id={fieldId} name='name' defaultValue={name} required pattern='.*\S.*' maxLength={80} autoFocus />
            </TextField>
          </div>
          <Button type='submit' size='sm' style={{ justifySelf: 'end', marginBlockStart: 'var(--spacing-8)' }}>Rename</Button>
        </form>
      </PopoverContent>
    </Popover>
  );
}
```

### Anchored User Menu

When to use:

- Account or context actions should stay visibly attached to the trigger.

Composition notes:

- Keep the content narrow and scannable.
- Do not turn the menu into a full settings surface.

Trade-offs:

- Strong spatial relationship to the trigger.
- Weaker fit once the content becomes form-heavy or destructive.

```tsx
import {
  Button,
  Popover,
  PopoverContent,
  PopoverTrigger,
  Separator,
  Text,
} from '@vega-ui/react';

export default function AnchoredUserMenu() {
  return (
    <Popover role='dialog' placement='bottom-end'>
      <PopoverTrigger asChild>
        <Button variant='secondary' appearance='ghost'>Maria Chen</Button>
      </PopoverTrigger>
      <PopoverContent aria-label='Account' style={{ padding: 'var(--spacing-2)' }}>
        <div style={{ display: 'grid', gap: 'var(--spacing-1)', minWidth: 220 }}>
          <div style={{ display: 'grid', paddingInline: 'var(--spacing-6)', paddingBlock: 'var(--spacing-4)' }}>
            <Text size={3} fontWeight={600}>Maria Chen</Text>
            <Text size={2} style={{ color: 'var(--label-secondary)' }}>maria@northwind.dev</Text>
          </div>
          <Separator />
          <Button asChild variant='secondary' appearance='transparent' style={{ justifyContent: 'flex-start' }}>
            <a href='#profile'>Profile</a>
          </Button>
          <Button asChild variant='secondary' appearance='transparent' style={{ justifyContent: 'flex-start' }}>
            <a href='#billing'>Billing</a>
          </Button>
          <Separator />
          <Button variant='secondary' appearance='transparent' style={{ justifyContent: 'flex-start' }}>Sign out</Button>
        </div>
      </PopoverContent>
    </Popover>
  );
}
```

## Comparison

### Quick Decision Rule

Choose `Popover` when the content is contextual, brief, and should stay anchored to a trigger without becoming modal.

### `Popover` vs `Tooltip`

Use `Popover` when:

- the content is richer than a simple hint
- it may contain actions or form controls

Use `Tooltip` when:

- the content is purely supplemental information

### `Popover` vs `Dialog`

Use `Popover` when:

- the interaction is non-modal and lightweight
- the user should keep page context and not enter a trapped modal flow

Use `Dialog` when:

- the task needs stronger modal focus and containment

### `Popover` vs `Drawer`

Use `Popover` when:

- the content is short and anchored

Use `Drawer` when:

- the content grows into a side-panel workflow

### Choose This Component When

- the content is contextual and brief
- the UI should stay non-modal
- anchored placement is part of the interaction model

### Do Not Choose This Component When

- the task is large, modal, or destructive enough to deserve a stronger overlay

## Troubleshooting

### Popover Feels Too Heavy

#### Symptom

The content technically works, but the overlay feels too big or modal.

#### Likely Cause

The task belongs in `Dialog` or `Drawer`.

#### How To Verify

- Review content length and task criticality.

#### Fix

- Switch to a stronger overlay when the flow is no longer lightweight.

### Placement Breaks In Scroll Containers

#### Symptom

The floating layer feels detached from the trigger.

#### Likely Cause

The real container or overlay stack was not tested.

#### How To Verify

- Place the popover inside the real scrolling or clipped layout.

#### Fix

- Retest the exact production container structure.
- `PopoverContent` renders in the top layer, so `overflow` and `z-index` on ancestors do not clip or cover it; look at placement instead.

### Nested Popovers Feel Confusing

#### Symptom

Open state and focus order become unclear with popovers inside popovers.

#### Likely Cause

Nested floating layers are being used for a task that needs a stronger pattern.

#### How To Verify

- Test nested open and close behavior.

#### Fix

- Simplify the flow or switch to a larger overlay pattern.

### Trigger Meaning Is Unclear Without The Popover

#### Symptom

Users only understand the trigger after the popover opens.

#### Likely Cause

The trigger relies on floating content as its primary label or explanation.

#### How To Verify

- Hide the popover and inspect whether the trigger still makes sense.
- Test with keyboard and screen reader flows.

#### Fix

- Give the trigger its own accessible meaning.
- Keep popover content supplemental and contextual.

### Popover Does Not Open From A Custom Trigger

#### Symptom

Clicking a custom `PopoverTrigger asChild` child does nothing.

#### Likely Cause

The child does not render a `<button>`, so the native `popovertarget` has no effect.

#### How To Verify

- Inspect the trigger element and check that it is a `<button>` with `popovertarget`.

#### Fix

- Use `Button` or another component that renders a `<button>` as the `asChild` child.
