Skip to main content

Popover

Display floating content that provides more contextual information.

When to Use

  • Need to provide additional explanatory information for page elements
  • When the information content is relatively rich and needs structured presentation
  • Need to display additional operational options or quick actions
  • Need to display information on hover or click without leaving the current page

The difference between Popover and Tooltip:

  • Tooltip: Simple text prompts, displayed on hover only, cannot contain complex content
  • Popover: Can display rich content, supports title, text, images, forms, etc., supports multiple trigger methods

In Kube Design, the Popover component provides flexible floating card functionality:

  • Multiple Trigger Modes: Support mouseenter, click, focus, manual trigger
  • 12 Position Options: Support display in 12 different directions
  • Interactive Content: Support user interaction with content (default enabled)
  • Controlled Mode: Support controlling display state through code

Examples

Basic Usage

Most basic Popover usage.

Live Editor
function Demo() {
  return (
    <Popover title="Title" content="This is the popover content, can contain more information">
      <Button>Hover me</Button>
    </Popover>
  );
}
Result
Loading...

Trigger Modes

Popover supports multiple trigger modes.

Live Editor
function Demo() {
  return (
    <Group spacing="md">
      <Popover title="Hover Trigger" content="Mouse over to display popover" trigger="mouseenter">
        <Button>Hover</Button>
      </Popover>
      <Popover title="Click Trigger" content="Click to display popover" trigger="click">
        <Button>Click</Button>
      </Popover>
      <Popover title="Focus Trigger" content="Focus to display popover" trigger="focus">
        <Button>Focus</Button>
      </Popover>
    </Group>
  );
}
Result
Loading...

Positions

Popover supports 12 different position options.

Live Editor
function Demo() {
  const positions = [
    ['top-start', 'top', 'top-end'],
    ['left-start', 'left', 'left-end'],
    ['right-start', 'right', 'right-end'],
    ['bottom-start', 'bottom', 'bottom-end'],
  ];

  return (
    <Group direction="column" spacing="xl">
      {positions.map((row, i) => (
        <Group key={i} spacing="md" style={{ justifyContent: 'center' }}>
          {row.map((placement) => (
            <Popover
              key={placement}
              title="Title"
              content={`Placement: ${placement}`}
              placement={placement}
            >
              <Button style={{ width: '120px' }}>{placement}</Button>
            </Popover>
          ))}
        </Group>
      ))}
    </Group>
  );
}
Result
Loading...

Custom Width

Set Popover width through the width property.

Live Editor
function Demo() {
  return (
    <Group spacing="md">
      <Popover
        title="Default Width"
        content="This popover uses the default width, automatically adapting to content width"
      >
        <Button>Default Width</Button>
      </Popover>
      <Popover
        title="Custom Width"
        content="This popover uses a custom width of 300px, suitable for longer content display"
        width={300}
      >
        <Button>Width 300px</Button>
      </Popover>
      <Popover
        title="Maximum Width"
        content="This popover sets a maximum width. When content is shorter, it automatically adjusts width; when longer, it wraps with a maximum width of 400px"
        maxWidth={400}
      >
        <Button>Max Width 400px</Button>
      </Popover>
    </Group>
  );
}
Result
Loading...

Content Only

Display content only without title.

Live Editor
function Demo() {
  return (
    <Popover content="This is pure content without title">
      <Button>No Title</Button>
    </Popover>
  );
}
Result
Loading...

Complex Content

Popover can contain complex content including text, lists, etc.

Live Editor
function Demo() {
  const content = (
    <div>
      <Text variant="h6" style={{ marginBottom: '8px' }}>
        Pod Information
      </Text>
      <Group direction="column" spacing="xs">
        <Text size="sm">Name: nginx-deployment-123</Text>
        <Text size="sm">Namespace: default</Text>
        <Text size="sm">Status: Running</Text>
        <Text size="sm">Restarts: 0</Text>
      </Group>
    </div>
  );

  return (
    <Popover title="Details" content={content} width={300}>
      <Button>View Pod Details</Button>
    </Popover>
  );
}
Result
Loading...

Content with Icons

Add icons to content to enhance information expression.

Live Editor
function Demo() {
  const { Cluster, Pod, Service } = KubedIcons;

  const content = (
    <Group direction="column" spacing="sm">
      <Group spacing="xs">
        <Cluster size={16} />
        <Text size="sm">Cluster: production-1</Text>
      </Group>
      <Group spacing="xs">
        <Pod size={16} />
        <Text size="sm">Pods: 12 Running</Text>
      </Group>
      <Group spacing="xs">
        <Service size={16} />
        <Text size="sm">Services: 5 Active</Text>
      </Group>
    </Group>
  );

  return (
    <Popover title="Cluster Status" content={content} width={250}>
      <Button>Cluster Info</Button>
    </Popover>
  );
}
Result
Loading...

Controlled Mode

Control Popover display state through visible and onVisibleChange.

Live Editor
function Demo() {
  const [visible, setVisible] = React.useState(false);

  return (
    <Group direction="column" spacing="md">
      <Text>Popover is: {visible ? 'Visible' : 'Hidden'}</Text>
      <Group spacing="xs">
        <Button size="sm" onClick={() => setVisible(true)}>
          Show
        </Button>
        <Button size="sm" variant="outline" onClick={() => setVisible(false)}>
          Hide
        </Button>
        <Button size="sm" variant="text" onClick={() => setVisible(!visible)}>
          Toggle
        </Button>
      </Group>
      <Popover
        title="Controlled Popover"
        content="This popover is controlled through code"
        visible={visible}
        onVisibleChange={setVisible}
        trigger="click"
      >
        <Button>Click Trigger</Button>
      </Popover>
    </Group>
  );
}
Result
Loading...

Manual Control

Manually control Popover display and hiding through ref.

Live Editor
function Demo() {
  const popoverRef = React.useRef(null);

  return (
    <Group direction="column" spacing="md">
      <Group spacing="xs">
        <Button size="sm" onClick={() => popoverRef.current?.show()}>
          Manual Show
        </Button>
        <Button size="sm" variant="outline" onClick={() => popoverRef.current?.hide()}>
          Manual Hide
        </Button>
      </Group>
      <Popover
        title="Manual Control"
        content="This popover is controlled through ref instance methods"
        trigger="manual"
        onMount={(instance) => {
          popoverRef.current = instance;
        }}
      >
        <Button>Target Element</Button>
      </Popover>
    </Group>
  );
}
Result
Loading...

Delayed Display

Control Popover display and hide delay through the delay property.

Live Editor
function Demo() {
  return (
    <Group spacing="md">
      <Popover title="No Delay" content="Display immediately" delay={0}>
        <Button>No Delay</Button>
      </Popover>
      <Popover title="Delay 500ms" content="Display after 500ms" delay={500}>
        <Button>Delay 500ms</Button>
      </Popover>
      <Popover title="Delay 1s" content="Display after 1 second" delay={1000}>
        <Button>Delay 1s</Button>
      </Popover>
    </Group>
  );
}
Result
Loading...

Information Display

Display additional explanatory information for page elements.

Live Editor
function Demo() {
  const { Information } = KubedIcons;

  const content = (
    <div style={{ maxWidth: '300px' }}>
      <Text size="sm">
        Deployment is a Kubernetes resource that manages the lifecycle of Pods. It provides
        declarative update strategies, supports rolling updates and rollback features, and ensures
        application high availability.
      </Text>
    </div>
  );

  return (
    <Group spacing="xs" align="center">
      <Text>What is Deployment?</Text>
      <Popover title="Deployment Explanation" content={content} placement="top">
        <Information
          size={16}
          style={{ cursor: 'pointer', color: '#79879c' }}
        />
      </Popover>
    </Group>
  );
}
Result
Loading...

Quick Actions

Display quick action options through Popover.

Live Editor
function Demo() {
  const { More, Pen, Copy, Download, Trash } = KubedIcons;
  const [action, setAction] = React.useState('');

  const content = (
    <Group direction="column" spacing="xs">
      <Button
        variant="text"
        size="sm"
        onClick={() => setAction('Edit')}
        style={{ justifyContent: 'flex-start', width: '100%' }}
      >
        <Pen size={16} style={{ marginRight: '8px' }} />
        Edit
      </Button>
      <Button
        variant="text"
        size="sm"
        onClick={() => setAction('Copy')}
        style={{ justifyContent: 'flex-start', width: '100%' }}
      >
        <Copy size={16} style={{ marginRight: '8px' }} />
        Copy
      </Button>
      <Button
        variant="text"
        size="sm"
        onClick={() => setAction('Download')}
        style={{ justifyContent: 'flex-start', width: '100%' }}
      >
        <Download size={16} style={{ marginRight: '8px' }} />
        Download
      </Button>
      <Divider style={{ margin: '4px 0' }} />
      <Button
        variant="text"
        size="sm"
        onClick={() => setAction('Delete')}
        style={{ justifyContent: 'flex-start', width: '100%', color: '#ca2621' }}
      >
        <Trash size={16} style={{ marginRight: '8px' }} />
        Delete
      </Button>
    </Group>
  );

  return (
    <Group direction="column" spacing="md">
      {action && <Text>You clicked: {action}</Text>}
      <Popover content={content} trigger="click" placement="bottom-end">
        <Button variant="text" style={{ padding: '4px 8px' }}>
          <More size={20} />
        </Button>
      </Popover>
    </Group>
  );
}
Result
Loading...

Form Input

Popover can contain form input.

Live Editor
function Demo() {
  const [name, setName] = React.useState('');
  const [visible, setVisible] = React.useState(false);

  const content = (
    <Group direction="column" spacing="md" style={{ width: '280px' }}>
      <div>
        <Text size="sm" style={{ marginBottom: '8px' }}>
          Resource Name:
        </Text>
        <Input
          placeholder="Please enter resource name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </div>
      <Group spacing="xs" style={{ justifyContent: 'flex-end' }}>
        <Button size="sm" variant="outline" onClick={() => setVisible(false)}>
          Cancel
        </Button>
        <Button
          size="sm"
          onClick={() => {
            alert(`Created: ${name}`);
            setVisible(false);
            setName('');
          }}
        >
          Confirm
        </Button>
      </Group>
    </Group>
  );

  return (
    <Popover
      title="Create Resource"
      content={content}
      trigger="click"
      visible={visible}
      onVisibleChange={setVisible}
    >
      <Button>Quick Create</Button>
    </Popover>
  );
}
Result
Loading...

List Information Display

Display list information in Popover.

Live Editor
function Demo() {
  const pods = [
    { name: 'nginx-1', status: 'Running' },
    { name: 'nginx-2', status: 'Running' },
    { name: 'nginx-3', status: 'Pending' },
  ];

  const content = (
    <Group direction="column" spacing="xs" style={{ width: '250px' }}>
      {pods.map((pod) => (
        <div
          key={pod.name}
          style={{
            display: 'flex',
            justifyContent: 'space-between',
            padding: '6px 8px',
            backgroundColor: '#f5f7fa',
            borderRadius: '4px',
          }}
        >
          <Text size="sm">{pod.name}</Text>
          <Badge
            variant="dot"
            color={pod.status === 'Running' ? 'success' : 'warning'}
            size="sm"
          >
            {pod.status}
          </Badge>
        </div>
      ))}
    </Group>
  );

  return (
    <Popover title="Pod List" content={content} placement="bottom">
      <Button>View Pods (3)</Button>
    </Popover>
  );
}
Result
Loading...

Status Details Display

Display detailed status information through Popover.

Live Editor
function Demo() {
  const { Cluster } = KubedIcons;

  const content = (
    <div style={{ width: '320px' }}>
      <Group direction="column" spacing="md">
        <div>
          <Text size="sm" color="secondary" style={{ marginBottom: '4px' }}>
            Cluster Name
          </Text>
          <Text>production-cluster-1</Text>
        </div>
        <div>
          <Text size="sm" color="secondary" style={{ marginBottom: '4px' }}>
            Kubernetes Version
          </Text>
          <Text>v1.24.3</Text>
        </div>
        <div>
          <Text size="sm" color="secondary" style={{ marginBottom: '4px' }}>
            Node Count
          </Text>
          <Group spacing="xs">
            <Badge color="success">Ready: 5</Badge>
            <Badge color="warning">NotReady: 1</Badge>
          </Group>
        </div>
        <div>
          <Text size="sm" color="secondary" style={{ marginBottom: '4px' }}>
            Resource Usage
          </Text>
          <div style={{ marginTop: '8px' }}>
            <div style={{ marginBottom: '8px' }}>
              <Group style={{ marginBottom: '4px', justifyContent: 'space-between' }}>
                <Text size="xs">CPU</Text>
                <Text size="xs">45%</Text>
              </Group>
              <Progress value={45} color="#55bc8a" size="sm" />
            </div>
            <div>
              <Group style={{ marginBottom: '4px', justifyContent: 'space-between' }}>
                <Text size="xs">Memory</Text>
                <Text size="xs">72%</Text>
              </Group>
              <Progress value={72} color="#f5a623" size="sm" />
            </div>
          </div>
        </div>
      </Group>
    </div>
  );

  return (
    <Popover title="Cluster Status" content={content} trigger="click" placement="bottom-start">
      <Button>
        <Cluster size={16} style={{ marginRight: '8px' }} />
        Cluster Details
      </Button>
    </Popover>
  );
}
Result
Loading...

API

Popover

Popover inherits all Tooltip properties and adds the following properties:

PropertyDescriptionTypeDefault
titlePopover titleReactNode-
contentPopover contentReactNodeRequired
widthPopover widthnumber-
maxWidthPopover maximum widthnumber-
placementPopover positionPlacement'top'
triggerTrigger mode'mouseenter' | 'click' | 'focus' | 'manual''mouseenter'
visibleWhether visible (controlled mode)boolean-
onVisibleChangeCallback when visibility changes(visible: boolean) => void-
interactiveWhether content is interactivebooleantrue
delayDisplay delay (milliseconds)number | [number, number]0
offsetPopover offset [horizontal, vertical][number, number][0, 4]
durationAnimation duration (milliseconds)number | [number, number][250, 200]
hideOnClickWhether to hide on clickboolean | 'toggle'true
disabledWhether disabledbooleanfalse
onMountCallback when Popover instance is created(instance: PopoverInstance) => void-
classNameCustom class namestring-
styleCustom stylesCSSProperties-
childrenTrigger elementReactElementRequired

PopoverInstance

Instance object obtained through the onMount callback, providing the following methods:

MethodDescriptionType
show()Show Popover() => void
hide()Hide Popover() => void
setProps()Dynamically update Popover props(props: Partial<PopoverProps>) => void
destroy()Destroy Popover instance() => void

Placement

Popover supports the following 12 position options:

ValueDescription
topTop center
top-startTop left
top-endTop right
bottomBottom center
bottom-startBottom left
bottom-endBottom right
leftLeft center
left-startLeft top
left-endLeft bottom
rightRight center
right-startRight top
right-endRight bottom

Additionally supports auto and auto-start, auto-end for automatic position calculation.

info

About Popover vs Tooltip:

  • Popover inherits all Tooltip functionality
  • Main differences:
    • Popover supports title property for structured content
    • Popover default interactive={true}, allowing interaction with content
    • Popover typically displays richer content (lists, forms, etc.)
    • Tooltip is more suitable for simple text prompts

About width settings:

  • width: Fixed width, content wraps beyond this width
  • maxWidth: Maximum width, content wraps beyond this width, narrower content automatically adjusts width
  • When both are not set, Popover automatically adapts to content width
  • Recommend setting appropriate width based on content to avoid too wide or too narrow

About trigger modes:

  • mouseenter: Display on hover, hide on mouse leave (default)
  • click: Display on click, click again or click outside to hide
  • focus: Display on element focus, hide on blur
  • manual: Fully manually controlled, requires using visible property or instance methods

About interactivity:

  • interactive={true} (default): Users can interact with Popover content (click buttons, input text, etc.)
  • interactive={false}: Popover hides when mouse leaves trigger element
  • When content contains clickable elements (buttons, links, etc.), must keep interactive={true}

About delay:

  • Single number: Both show and hide delay are this value
  • Array [showDelay, hideDelay]: Separately control show and hide delay
  • Unit is milliseconds
  • Setting appropriate delay can avoid frequent display/hiding

About offset:

  • Format: [horizontal, vertical]
  • Horizontal offset: Positive moves right, negative moves left
  • Vertical offset: Positive moves down, negative moves up
  • Default [0, 4] means 4px spacing from trigger element

About animation:

  • duration: Control animation duration, can be single number or array
  • Single number: Both show and hide animations use this duration
  • Array [showDuration, hideDuration]: Separately control show and hide animation duration
  • Default [250, 200] means 250ms show, 200ms hide

About hide behavior:

  • hideOnClick={true}: Hide Popover when clicking trigger element
  • hideOnClick={false}: Click trigger element won't hide Popover
  • hideOnClick='toggle': Click trigger element toggles Popover show/hide state
  • Click outside Popover always hides (when trigger is not manual)

Usage Recommendations

Popover vs Tooltip Selection

Choose appropriate component based on content complexity:

// Simple text prompt: use Tooltip
<Tooltip content="Click to create new resource">
<Button>Create</Button>
</Tooltip>

// Rich content: use Popover
<Popover
title="Create Resource"
content={
<div>
<Text>Select resource type:</Text>
<Button>Deployment</Button>
<Button>Service</Button>
</div>
}
>
<Button>Create</Button>
</Popover>

Set Appropriate Width

Set appropriate width based on content:

// Short content: no need to set width, auto adapt
<Popover content="Simple prompt">
<Button>Hover</Button>
</Popover>

// Long content: set fixed width
<Popover
content="This is a long content description that needs to wrap to display well"
width={300}
>
<Button>Details</Button>
</Popover>

// Variable content: set max width
<Popover
content={dynamicContent}
maxWidth={400}
>
<Button>View</Button>
</Popover>

Choose Appropriate Trigger Mode

Choose trigger mode based on scenario:

// Information display: use hover trigger
<Popover title="Help" content="..." trigger="mouseenter">
<HelpIcon />
</Popover>

// Operations and forms: use click trigger
<Popover title="Quick Create" content={<CreateForm />} trigger="click">
<Button>Create</Button>
</Popover>

// Input assistance: use focus trigger
<Popover title="Format Requirements" content="..." trigger="focus">
<Input placeholder="Enter" />
</Popover>

Use Controlled Mode

Use controlled mode when need to control display state:

const [visible, setVisible] = useState(false);

// External control display
<div>
<Button onClick={() => setVisible(true)}>Show Details</Button>
<Popover
content="Detailed information"
visible={visible}
onVisibleChange={setVisible}
>
<div>Target Element</div>
</Popover>
</div>

Manual Control via ref

Use ref for more flexible control:

const popoverRef = useRef(null);

const handleAction = () => {
// Show Popover when certain operation completes
popoverRef.current?.show();

// Hide after 2 seconds
setTimeout(() => {
popoverRef.current?.hide();
}, 2000);
};

<Popover
content="Operation successful"
trigger="manual"
onMount={(instance) => {
popoverRef.current = instance;
}}
>
<Button onClick={handleAction}>Execute</Button>
</Popover>

Set Appropriate Delay

Set delay to optimize user experience:

// Information icon: set slight delay to avoid accidental triggers
<Popover content="Help information" delay={300}>
<InfoIcon />
</Popover>

// Important prompt: no delay, display immediately
<Popover content="Important notification" delay={0}>
<WarningIcon />
</Popover>

// Different show/hide delays
<Popover content="Content" delay={[500, 0]}>
<Button>Hover</Button>
</Popover>

Position Selection

Choose appropriate position based on element location:

// Top area element: display below
<Popover placement="bottom" content="...">
<Button>Top Button</Button>
</Popover>

// Bottom area element: display above
<Popover placement="top" content="...">
<Button>Bottom Button</Button>
</Popover>

// Right area element: display on left
<Popover placement="left" content="...">
<Button>Right Button</Button>
</Popover>

// Auto position: let component choose best position
<Popover placement="auto" content="...">
<Button>Auto Position</Button>
</Popover>

Interactive Content Considerations

Keep interactive={true} when content contains interactive elements:

// Content contains buttons, links, etc.
<Popover
interactive={true} // Default is true
content={
<div>
<Button onClick={handleEdit}>Edit</Button>
<Button onClick={handleDelete}>Delete</Button>
</div>
}
>
<Button>Actions</Button>
</Popover>

Use Title for Structured Content

Add title for clear structure:

// With title: clear structure
<Popover
title="Pod Status"
content={
<div>
<Text>Running: 10</Text>
<Text>Pending: 2</Text>
</div>
}
>
<Button>View Status</Button>
</Popover>

// Pure information: no need for title
<Popover content="Click to view details">
<InfoIcon />
</Popover>

Form Input Best Practices

Best practices when Popover contains forms:

const [visible, setVisible] = useState(false);
const [formData, setFormData] = useState({});

<Popover
title="Quick Edit"
trigger="click"
visible={visible}
onVisibleChange={setVisible}
content={
<Form
onSubmit={(data) => {
handleSubmit(data);
setVisible(false); // Hide after submit
}}
>
<Input name="name" />
<Button type="submit">Confirm</Button>
<Button onClick={() => setVisible(false)}>Cancel</Button>
</Form>
}
>
<Button>Edit</Button>
</Popover>

Accessibility Considerations

Ensure Popover is keyboard accessible:

// Use button as trigger element, supports keyboard navigation
<Popover content="Help information" trigger="focus">
<Button>Help</Button>
</Popover>

// For non-interactive elements, add appropriate ARIA attributes
<Popover content="Explanation">
<span role="button" tabIndex={0}>
Explainable Text
</span>
</Popover>

Avoid Nested Popovers

Avoid nesting Popovers as it affects user experience:

// Not recommended: nested Popovers
<Popover content="Outer">
<Popover content="Inner">
<Button>Button</Button>
</Popover>
</Popover>

// Recommended: flat structure
<div>
<Popover content="Info 1">
<Button>Button 1</Button>
</Popover>
<Popover content="Info 2">
<Button>Button 2</Button>
</Popover>
</div>

Mobile Responsiveness

Consider mobile device experience:

// Mobile: use click trigger
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

<Popover
trigger={isMobile ? 'click' : 'mouseenter'}
content="..."
>
<Button>View</Button>
</Popover>

// Mobile: set appropriate width
<Popover
width={isMobile ? 280 : 400}
content="..."
>
<Button>Details</Button>
</Popover>