Skip to content

Popover

Source

Example

Vanilla Popover

html
<div class="x-popover focus">
    <div role="button" class="x-button" tabindex="0">Toggle Popover</div>
    <div class="x-popover-content center shadow dark:bg-body-secondary mt-2.5 p-2 w-32 flex-col">
        Popover content
    </div>
</div>

Javascript Popover

Leverages the Popover API with floating-ui.

html
<div
    class="x-popover"
    data-controller="x-popover"
>
    <button
        class="x-button"
        role="button"
        popovertargetaction="toggle"
        popovertarget="popoverElement"
        id="popoverAction"
    >Toggle Popover</button>
    
    <div class="x-popover-content shadow h-48" id="popoverElement" aria-describedby="popoverAction" popover="manual">
        Popover content
        <button
            type="button"
            data-action="x-popover#hide"
        >x</button>
    </div>
</div>

Autocomplete Popover

Preview

Source

html
<div class="x-popover trigger-focus group mx-auto w-80" data-controller="x-popover">
    <div class="x-control w-full">
        <input
            data-action="input->x-popover#autocomplete"
            data-x-popover-target="autocomplete"
            type="search"
            placeholder="Vyhledávání ..."
            autocomplete="off"
            role="combobox"
            aria-autocomplete="list"
            aria-controls="popoverContent"
            aria-haspopup="listbox"
            aria-expanded="true"
            value="Volba"
        >
    </div>
    <div
        class="x-popover-content center flex flex-col shadow dark:bg-body-secondary mt-2 p-2 gap-2 w-full empty:closed group-not-has-aria-expanded:closed"
        data-x-popover-target="content"
        role="listbox"
        id="popoverContent"
    >
        <button id="option-1" class="x-button ghosted justify-start" role="option" data-action="click->x-popover#selectDescendant">Volba 1</button>
        <button id="option-2" class="x-button ghosted justify-start" role="option" data-action="click->x-popover#selectDescendant">Volba 2</button>
        <button id="option-3" class="x-button ghosted justify-start" role="option" data-action="click->x-popover#selectDescendant">Volba 3</button>
    </div>
</div>

More examples can be found in the Winduum docs.

Example CSS

css
@import "winduum/src/components/popover/props/content.css" layer(theme);
@import "winduum/src/components/popover/content.css" layer(utilities);
@import "winduum/src/components/popover/default.css" layer(utilities);

/* Example customization */
@layer utilities {
  .x-popover {
    --x-popover-content-background-color: var(--color-accent);
    --x-popover-content-border-radius: var(--radius-md);
  }
}

Winduum CSS Code

winduum/src/components/popover/props/content.css

css
:root, :host {
  --x-popover-content-background-color: var(--color-body-primary);
  --x-popover-content-border-radius: var(--radius-xl);
  --x-popover-content-padding-block: calc(var(--spacing) * 2);
  --x-popover-content-padding-inline: calc(var(--spacing) * 2);
  --x-popover-content-scale-y: 0.75;
  --x-popover-content-scale-x: 0.75;
}

winduum/src/components/popover/content.css

css
.x-popover-content {
  background-color: var(--x-popover-content-background-color);
  border-radius: var(--x-popover-content-border-radius);
  padding: var(--x-popover-content-padding-block) var(--x-popover-content-padding-inline);
  z-index: var(--x-popover-content-z-index, var(--z-index-10));
  transition-property: var(--default-transition-property);
  transition-timing-function: var(--ease-in-out);
  transition-duration: var(--default-transition-duration);
  transform:
    translate(var(--tw-translate-x, 0), var(--tw-translate-y, 0))
    scaleX(var(--tw-scale-x, 1)) scaleY(var(--tw-scale-y, 1));
  will-change: transform;
  inline-size: max-content;
  position: absolute;

  &:where(.bottom), & {
    transform-origin: top;
  }

  &:where(.bottom-start) {
    transform-origin: top left;
  }

  &:where(.bottom-end) {
    inset-inline-end: 0;
    transform-origin: top right;
  }

  &:where(.right) {
    inset-block-start: 0;
    inset-inline-start: 100%;
    transform-origin: left;
  }

  &:where(.right-start) {
    transform-origin: left top;
  }

  &:where(.right-end) {
    inset-block: auto 0;
    transform-origin: left bottom;
  }

  &:where(.left) {
    inset-block-start: 0;
    inset-inline-end: 100%;
    transform-origin: right;
  }

  &:where(.left-start) {
    transform-origin: right top;
  }

  &:where(.left-end) {
    inset-block: auto 0;
    transform-origin: right bottom;
  }

  &:where(.top) {
    inset-block-end: 100%;
    transform-origin: bottom;
  }

  &:where(.top-start) {
    transform-origin: bottom left;
  }

  &:where(.top-end) {
    inset-inline-end: 0;
    transform-origin: bottom right;
  }

  &:where(.inline-center) {
    --tw-translate-x: -50%;

    inset-inline-start: 50%;
  }

  &:where(.block-center) {
    --tw-translate-y: -50%;

    inset-block-start: 50%;
  }

  &[popover]:not([data-open]) {
    --tw-scale-x: var(--x-popover-content-scale-x);
    --tw-scale-y: var(--x-popover-content-scale-y);

    opacity: 0%;
    pointer-events: none;
  }

  &:not([popover]) {
    .trigger-focus:not(:focus, :focus-within) > &, .trigger-hover:not(:hover) > & {
      --tw-scale-x: var(--x-popover-content-scale-x);
      --tw-scale-y: var(--x-popover-content-scale-y);

      opacity: 0%;
      visibility: hidden;
    }
  }
}

winduum/src/components/popover/default.css

css
.x-popover {
  position: relative;
  display: inline-block;
}

Stimulus Actions

toggle

Action is automatically added on first [popovertargetaction] element upon controller connection, if it exists.

html
<button 
    class="x-button" 
    role="button" 
    popovertargetaction="toggle" 
    data-action="click->x-popover#toggle:prevent keydown.esc@window->x-popover#hide click@window->x-popover#dismiss click->invoke-ripple#show" 
    popovertarget="_6zbalmci_" 
    aria-expanded="false" 
    aria-haspopup="dialog"
>
    Toggle Popover
</button>

show

Action is automatically added on first [popovertargetaction] element upon controller connection, if it exists.

hide

Action is automatically added on first [popovertargetaction] element upon controller connection, if it exists.

html
    <div class="x-popover-content shadow h-48" id="popoverElement" aria-describedby="popoverAction" popover="manual">
        Popover content
        <button
            type="button"
            data-action="x-popover#hide"
        >x</button>
    </div>

dismiss

Action is automatically added on first [popovertargetaction] element upon controller connection as click@window->x-popover#dismiss, if it exists.

fetch

Fetches the popover remotely, used internally.

onFetchComplete

Callback that is executed after fetch is complete.

Stimulus Values

url

Fetches the popover remotely, popovertarget is added dynamically upon completed request.

html
    <div
        class="x-popover"
        data-controller="x-popover"
        data-x-popover-url-value="/popover/info.json"
    >
        <button
            class="x-button"
            role="button"
            popovertargetaction="toggle"
            id="popoverAction"
        >Fetch Popover</button>
    </div>

appendTo

Choose a selector where to append the popover after a fetch. Defaults to position after the trigger element of the popover.

html
    <div
        class="x-popover"
        data-controller="x-popover"
        data-x-popover-url-value="/popover/info.json"
        data-x-popover-append-to-value="body"
        id="popoverAction"
    >
        <button
            class="x-button"
            role="button"
            popovertargetaction="toggle"
        >Fetch Popover</button>
    </div>

manual

Disables automatic generation of action values to [popovertargetaction] element upon controller connection.

html
<div
    class="x-popover"
    data-controller="x-popover"
    data-x-popover-manual-value="true"
>
    <button
        class="x-button"
        role="button"
        popovertargetaction="toggle"
        popovertarget="popoverElement"
    >Toggle Popover</button>
</div>

Released under the MIT License.