Published on

How to Close Radix/Shadcn Popover from Inside

Authors
  • Name
    Ripal & Zalak
    Twitter

Introduction

Radix and Shadcn provide flexible UI primitives, including the Popover component, which is highly customizable. However, developers often face challenges when trying to close a Popover programmatically from inside its content. This guide explains multiple approaches to achieve this.


The Problem

The Popover component doesn’t provide a direct built-in method to close itself programmatically from inside its content. A typical use case is having buttons inside the PopoverContent that trigger specific actions and close the Popover afterward. Without proper handling, the Popover remains open.


Solutions

1. Using Controlled State

Manage the Popover's open state manually using the open and onOpenChange props. This approach ensures full control over when the Popover opens and closes.

Example:

import { useState } from 'react'
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'

export function ControlledPopover() {
  const [isOpen, setIsOpen] = useState(false)

  const handleButtonClick = () => {
    // Perform your action here
    console.log('Button clicked!')
    setIsOpen(false) // Close the popover
  }

  return (
    <Popover open={isOpen} onOpenChange={setIsOpen}>
      <PopoverTrigger asChild>
        <button>Open Popover</button>
      </PopoverTrigger>
      <PopoverContent>
        <button onClick={handleButtonClick}>Close Popover</button>
      </PopoverContent>
    </Popover>
  )
}

2. Using PopoverClose

Radix UI provides a PopoverClose component, which allows you to close the Popover when interacting with an element inside it. This is a simple and declarative solution.

Example:

import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'
import { PopoverClose } from '@radix-ui/react-popover'

export function DeclarativePopover() {
  return (
    <Popover>
      <PopoverTrigger asChild>
        <button>Open Popover</button>
      </PopoverTrigger>
      <PopoverContent>
        <PopoverClose asChild>
          <button>Close Popover</button>
        </PopoverClose>
      </PopoverContent>
    </Popover>
  )
}

3. Combining Actions with onOpenChange

You can handle the closing of the Popover in combination with custom actions by listening to the onOpenChange event.

Example:

import { useState } from 'react'
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'

export function PopoverWithCustomActions() {
  const [isOpen, setIsOpen] = useState(false)

  const handleSubmit = () => {
    console.log('Action executed')
    setIsOpen(false) // Close the popover
  }

  return (
    <Popover open={isOpen} onOpenChange={setIsOpen}>
      <PopoverTrigger asChild>
        <button>Open Popover</button>
      </PopoverTrigger>
      <PopoverContent>
        <form
          onSubmit={(e) => {
            e.preventDefault()
            handleSubmit()
          }}
        >
          <button type="submit">Submit & Close</button>
        </form>
      </PopoverContent>
    </Popover>
  )
}

Best Practices

  • Controlled State: Use controlled components when you need precise control over the Popover's behavior.
  • Declarative Approach: Utilize PopoverClose for simpler use cases where you don’t need additional logic.
  • Accessibility: Ensure all actions are accessible and provide clear feedback to the user.

FAQs

1. Can I use PopoverClose and controlled state together? Yes, but it’s usually unnecessary since controlled state gives you complete control over the Popover.

2. Why doesn’t my Popover close when clicking outside? Ensure you’re using onOpenChange to manage the Popover state. Radix UI automatically handles outside clicks if state is properly managed.

3. Is it possible to animate the Popover closing? Yes, Radix UI supports animations via the asChild prop and CSS transitions. You can wrap the PopoverContent with a custom animated component.


Conclusion

Closing a Shadcn or Radix Popover from inside its content is straightforward when using controlled state or the PopoverClose component. Choose the approach that best suits your use case, and always ensure a smooth and accessible user experience.