Skip to content

Hover Activated Dropdown (B)

Example

A commonly used web component is a Button which reveals a list of choices when activated.
Hover Activated Dropdown

Description

The example above is an Astro component comprised of a <button> and a dropdown container which wraps a <ul> containing a list of choices.

The unordered list is populated by passing multiple <li> into the component’s default slot.

Hover Activated Dropdown Example
---
import HoverDropdownMenu_B from '/components/HoverDropdownMenu_B.astro';
const tw = {
menuItem: 'px-4 hover:bg-stone-200 hover:dark:bg-stone-300 ' +
'hover:text-cyan-100 hover:dark:text-sky-950',
icon:'inline-block size-6'
}
cexport const hs = "on click send closeDropdown to the #{'hover_dropdown_id'} then settle then call alert('You selected ' + my innerHTML) "
---
<HoverDropdownMenu_B id="hover_dropdown_id" caption="Hover Me" duration="300ms" moveXY="10, 5">
<svg slot="rightIcon" class={tw.icon} fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/>
</svg>
<li class={tw.menuItem} script={hs}>Choice 1</li>
<li class={tw.menuItem} script={hs}>Choice 2</li>
<li class={tw.menuItem} script={hs}>Choice 3</li>
<li class={tw.menuItem} script="on click send closeDropdown to #{'hover_dropdown_id'} then settle then go to url '/' ">Go Home</li>
</HoverDropdownMenu_B>

The dropdown container is hidden by setting it’s height to 0 and the opacity of it’s <ul> to 0.

The <button> listens for either a mouseenter or click event which then triggers the items to smoothly appear by transitioning the dropdown container to a new height and the <ul>’s opacity to 1 over a specified duration.

Tapping any <li> emits a custom event from a script attribute followed by dismissal of the dropdown.

Features

The trigger button and dropdown are initally styled with Tailwind CSS utility classes but are fully customizable by passing additional classes through props.

The dropdown appearance/disappearance is smoothly animated by HyperScript with a timing function adjusted using the duration prop.

The dropdown is dismissed by:

1. Clicking the trigger button again
2. Clicking any menu item
3. Mouse leaving the button
4. Mouse leaving the dropdown
5. Pressing the Escape key when the button is focused

Custom events emitted by individual menu choices were also written in HyperScript.

Props

Below is a summary of the component props which can optionally be used to configure the component from the markup where it is being used.

interface Props {
id: string,
caption?: string,
buttonStyles?: string,
dropdownStyles?: string,
listStyles?: string,
moveXY?: string,
duration?: string
}
const {
id,
caption,
buttonStyles,
dropdownStyles,
listStyles,
moveXY = "0,0",
duration = '100ms'
} = Astro.props

id

Each dropdown component requires a unique id. This is to ensure that multiple dropdowns do not interfere with each other.

caption

The caption prop is a text string that will be displayed in the trigger button.

buttonStyles

The buttonStyles prop allows you to customize the appearance of the trigger button. Use this prop if you want to change the button’s hover styles, background or text color, apply a dropshadow, etc.

listStyles

The listStyles prop allows you to customize the appearance of the <ul> flex container. Any styles you pass in this prop apply to the parent <ul> that wraps all menu items.

The dropdownStyles prop allows you to style the dropdown container itself.

moveXY

If the dropdown needs to be moved relative to the trigger button, you can pass a string with two comma separated values using the moveXY prop. The first value is the horizontal shift and the second is the vertical shift.

For example, to move the dropdown 5 pixels to the left and 5 pixels down relative to the button’s location, you would pass "-5,5".

duration

The duration of the height transition for the dropdown container can be specified by passing a string (in milliseconds) into the duration prop, for example “300ms”.

Styling

The component’s <button>, <ul> and dropdown container are initally styled with a default selection of Tailwind classes. For accesssibility, the trigger <button> also receives a focus ring when activated.

You can override the default styling by sending additional Tailwind classes via props which are then merged with the default styles and any conflicts resolved with a ‘last wins’ rule.

To improve readability, the Tailwind classes are extracted from the markup and encapsulated into an Astro tw local variable within the Component Script which is then referenced in the html markup.

The advantage of this strategy is to allow long run-on strings to be formatted as concatenated, multi-line strings which aids readability and code maintenance.

Transitions

Hyperscript exposes the transition keyword so you can explicitly utilize CSS selectors to invoke transitions within your HS code. However, this technique is blocking, and you will need to explicitly call the settle keyword after each transition statement which tells HS to wait briefly (20ms by default) to allow for swapping of all HS specific CSS classes used to implement the transition.

A simpler, but less flexible, technique is to apply a CSS transition rule directly on the element being transitioned using an inline <style> tag. This technique is not blocking and reduces the amount of code in the script itself. This is the technique chosen for this hyperComponent.

Slots

Default Slot

All menu items should be placed between the component’s opening and closing tags thus positioning them within the default slot inside the component’s <ul>.

Left Icon Slot

The leftIcon slot is used to install any icon or image markup of your choice. If you also pass a caption prop, the leftIcon will be displayed to the left of the caption text.

Right Icon Slot

The rightIcon slot is used to install any icon or image markup of your choice which is then positioned to the right of the caption text.

In the example code above, a down-caret svg is used to present a visual clue that interacting with the <button> will reveal additional choices.

Icon Styling

In Astro, you target a named slot by including a slot="name" attribute in the parent element of the markup you are passing into the slot.

You will need to attach Tailwind styling classes to your slot markup because the receiving component has no way to apply Tailwind classes to elements it does not know about yet.

For example, icon sizes, padding, margins, and colors will need to be annotated directly on the markup passed into any <slot>.

The code below illustrates Tailwind styling of a down-caret svg passed into the rightIcon slot.

Tailwind Styling for Icon Slots
<svg slot="rightIcon" class="inline-block size-6" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/>
</svg>

Code

The full source code for this hyperComponent is visible in the various Tabs below. Use the Everything tab to copy/paste the entire component into your Astro project.

interface Props {
id: string //a unique identifier for this component, allowing multiple dropdowns on a page without collisions
caption?: string //the text displayed in the trigger button
buttonStyles?: string //any additional Tailwind classes for customizing the trigger button
dropdownStyles?: string //any additional styles to apply to the dropdown container <div>
listStyles?: string //any additional styles to apply to the <ul>
moveXY?: string //comma separated integer values specifying x,y shift in dropdown position
duration?: string, //duration (in milliseconds) for dropdown height transition
}
const {
id,
caption,
buttonStyles,
dropdownStyles,
listStyles,
moveXY = "0,0",
duration = '100ms'
} = Astro.props

Usage

The most common use case for dropdowns are in navigation components such as a top <nav> bar or an <aside> navigation drawer. Dropdowns can also provide a more customizable equivalent to <select> elements.

To use this component ‘as is’ in your Astro project, click the Copy button on the Everything tab in the code section above. Paste all the code into a new file with the .astro extension.

To use this hyperComponent in another Astro component just import the file and use angle brackets (with a Capitalized name) to create a custom html tag as shown in the example below.

UsingADropdown.astro
---
import HoverDropdownMenu_B from '/components/HoverDropdownMenu_B.astro';
const tw = {
menuItem: 'px-4 hover:bg-gray-200 hover:text-purple-600 ' +
'hover:dark:bg-stone-300 hover:dark:text-sky-800',
icon:'inline-block size-6'
}
const hs = "on click call alert('You selected ' + my innerHTML) "
---
<HoverDropdownMenu_B id="hover_dropdown_id" caption="Hover Me" duration="300ms" moveXY="10, 5">
<svg slot="rightIcon" class={tw.icon} fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/>
</svg>
<li class={tw.menuItem} script={hs}>Choice 1</li>
<li class={tw.menuItem} script={hs}>Choice 2</li>
<li class={tw.menuItem} script={hs}>Choice 3</li>
<li class={tw.menuItem} script="on click send closeDropdown to #{'hover_dropdown_id'} then settle then go to url '/' ">Go Home</li>
</HoverDropdownMenu_B>

To install menu items into the dropdown container just include multiple <li> elements between the component tags.

To attach custom actions to any menu item simply include a script attribute in the corresponding <li> (as seen in the example above).

Your custom scripts should always send closeDropdown to the #{'hover_dropdown_id'} then settle to dismiss the dropdown gracefully before proceeding with any further actions (as in the ‘Go Home’ choice above).