fast
fast copied to clipboard
rfc: add `MenuButton`
MenuButton
Overview
As defined by the W3C:
A menu button is a button that opens a menu. It is often styled as a typical push button with a downward pointing arrow or triangle to hint that activating the button will display a menu.
Background
A common request in the FAST community is how to make dropdowns, either for action menus or navigation. It is also a very common pattern amongst UI libraries, and while it is something that can produced using a combination of components that already exist in FAST it still a difficult pattern to implement correctly when accessibility becomes involved.
Use Cases
- Jon wants to find a specific service offered by a company, so he clicks on the services menu button in the navbar to see a menu of all the services the company has to offer.
- Jane wants to learn more about her favorite brand, so she clicks on the company menu button to see a menu of links to the brand's contact, about us, and careers pages.
- Joe is writing a blog post using a wysiwyg editor and clicks on a menu button in the editor's toolbar to see a list of formatting actions available.
Features
-
Expanded/Collapsed Indicator: An indicator (usually down/up chevrons) that visually indicates the expanded/collapsed state of the menu, as well as indicating that the button opens a menu.
-
MenuPosition: Provides a way to set the position of the menu relative to the reference button.
Risks and Challenges
-
Should
Menube used, or should a custom menu that uses the styling fromMenube used? (similiar to howSelect's andCombobox's listboxes are built.) -
If
Menuis used, will it need to be updated to supportAnchors with a role ofmenuitem.Menualready supports slotting elements with amenuitemrole, but this poses a styling challenge.- Alternatively, could
MenuItembe updated to support the capabilities ofAnchoror should a purpose built element exist for menu items that are used for navigation?
- Alternatively, could
-
Could
MenuButtonalso be used for a split button?
Prior Art/Examples
- ARIA APG Navigation MenuButton
- Stripe.com's mega menu
- WinUI's Menu Flyout
- Adobe Spectrum's Action Menu
Design
API
Component Name:
fast-menu-button
Attributes & Properties:
menu-position- enum that contains all of the valid positions for the menu.viewport-lock- boolean that enables the menu to stay within the view.menu-width- enum with options for auto, reference width, and content widthmenu-height- enum with options for auto, reference height, and content heightmenu-offset- number. This could be an attribute, but leaning more towards a design token since this tends be something shared amongst popups that have a reference element in other design systems, i.e. Fluent UI'sSelect&Combobox.
Events
expand- fires when the menu is expandedcollapse- fires when the menu is collapsed
Anatomy
Notes:
-
When the host element gets connected to the DOM, it will need to setup a click handler on the document in order to trigger the light dismiss of the menu.
-
Some design systems add an arrow element to the menu that points at the button element. This can either be handled through component anatomy, or if floating-ui is used for the positioning, its arrow middleware can be used.
<!-- shadow dom -->
<button
id="{buttonId}"
class="control"
part="control"
aria-haspopup="true"
aria-controls="{menuId}"
aria-expanded="{expanded || null}"
>
<slot name="start"></slot>
<slot></slot>
<slot name="end"></slot>
<span class="indicator" part="indicator">
<slot name="expanded-icon">
{MenuButtonOptions.expandedIcon}
</slot>
<slot name="collapsed-icon">
{MenuButtonOptions.collapsedIcon}
</slot>
</span>
</button>
<div
id="{menuId}"
class="menu"
part="menu"
role="menu"
aria-labelledby="{buttonId}"
>
<slot name="item">
Menu items / dividers and other presentation content go here.
Anything that is not `role=menuitem` is ignored by keyboard navigation
</slot>
</div>
Slot Names
default- use the default slot for the button contentstart- the start slot before the button's content slotend- the end slot after the button's content and before the indicator slotsexpanded-icon- the icon to show the expanded state of the menucollapsed-icon- the icon to show the collapsed state of the menuitem- the menu items
CSS Parts
control- the button elementindicator- the wrapper element around the expanded-icon & collapsed-icon slotsmenu- the menu element
Implementation
States
expanded- if the menu is visible
Accessibility
The MenuButton should align to the patterns provided by the W3C: https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/
Globalization
The MenuButton will need to reverse the start and end slots, as well as the indicator for users in an RTL setting. The menu positions for start and end will also need to reverse as well.
Dependencies
-
Potentially floating-ui, if FAST ends up moving in that direction
-
If floating-ui is not used, then
AnchoredRegion -
Could use
Button,Menu, andMenuItemif the implementation uses composition in favor of custom anatomy that is styled like these components.- There is an accessibility concern with reusing
ButtoninMenuButton. While prototyping withButtonNVDA announces that the menu button is clickable twice, while using a native button does not have this behavior.
- There is an accessibility concern with reusing
Appendix
Resources
W3C Spec for Menu, Menubar, and MenuButton W3C Accessible MenuButton Example
I don't how I missed #5469 when looking for existing issues tracking this.
Thanks for this! One immediate thought that I have here is that I think the current RFC details two implementations. What I mean by that is that I think this illustrates both the foundation implementation, as well as what I would expect from the CLI. Specifically, I wonder about the "universal" applicability of menu-width, menu-height, and menu-offset. I really like these for the "FAST CLI" starter implementation, where those can be driven by tokens, but I don't think there are necessary for every menu button (foundational). Thoughts on that distinction @KingofTac? Again, I really like those, but they aren't necessary for an implementation where I may have a menu button with a menu that is holistically controlled by CSS as one example.
From a foundation point of view, I agree they aren't needed.
Getting into the implementation specifics, I would say if floating-ui is used by the foundation class, it would be pretty easy for implementations to provide their own version of computePosition and add the specific middewares and positioning details that they want. So with that in mind the CLI starter could very well add the menu-width and menu-height as tokens and override the foundation MenuButton's computePosition and pass it the size middleware using those tokens.
From a foundation point of view, I agree they aren't needed.
Getting into the implementation specifics, I would say if floating-ui is used by the foundation class, it would be pretty easy for implementations to provide their own version of
computePositionand add the specific middewares and positioning details that they want. So with that in mind the CLI starter could very well add themenu-widthandmenu-heightas tokens and override the foundationMenuButton'scomputePositionand pass it the size middleware using those tokens.
This was exactly the consideration I had when working to incorporate Floating-UI into a couple of our components as part of #6186. My thought was that while it might make for a robust API, exposing everything would make for a very "cluttered" API by default. Instead, I figured a better way might be to have a public method that could be overridden to provide a more custom and bespoke implementation of computePosition (something like setMenuPosition()).
A couple of follow-up thoughts/questions here:
-
Ideally I'd like to be able to use menu, because that's a not-insignificant portion of code that does represent a menu. As noted, it doesn't represent all types of menu, but we can look to pick that work up either as part of menu or by creating a more common navigation "menu" approach (list w/ links) for example...
-
If we have labelledby in the shadow dom for the menu, but the role of menu is on a slotted element, does that do what we expect accessibility wise? I'm not sure it's going to pick it up as expected.
-
Do you have an example of the implementation/prototype that was causing issues w/ NVDA (is that in the WIP examples)?
-
I think using
Menuis perfectly doable. The challenges with it are, like you said it doesn't represent all menus. -
This comment made me realize I missed adding the menu role in the anatomy section, so I went and updated that above. I think you're right that
labelledbywon't cross the shadow boundary so that's why I intended the menu element to be a part ofMenuButton's anatomy. I can see cases where you would want to slot in a custom menu element, so thoughts/ feedback here would be great. -
I'll have to find where I have that prototype since that implementation was from several months ago. I'll link here when I find it.
@chrisdholt Not a fully functioning menu button, but this shows the Behavior I was seeing with NVDA and using Button https://stackblitz.com/edit/typescript-lh92fh?file=index.ts
When the menu button receives focus, it will announce that it is clickable more than once
@chrisdholt I'm just circling back to this. If you want, I can update the spec to be focused on the foundation implementation with notes on extending and implementing custom versions.
@chrisdholt I'm just circling back to this. If you want, I can update the spec to be focused on the foundation implementation with notes on extending and implementing custom versions.
I think that would be great. Having the spec focus on Foundation and then illustrate extensibility as examples will transition well for the CLI and documentation IMO. I'd love if our documentation broadened to open up examples of extending classes to address common scenarios
Sounds good. I'll get a new version up this weekend.
Hi, I'm interested in building a split button and dropdown button components for FAST. What's the status of this project? Obviously you've got the spec here, but has any work been done to implement this?
I see two other issues asking for similar stuff:
Also, there was a (discarded?) PR to add a split button in the past.
So stepping back a moment, two questions:
- For @KingOfTac what's the status of this menu button? Has code been written yet?
- For @chrisdholt, I'm hesitant to contribute here for fear my work would be discarded. Would FAST welcome a MenuButton, SplitButton, and NavBar? Or is this something you guys don't want, or would rather build yourselves?
Hi, I'm interested in building a split button and dropdown button components for FAST. What's the status of this project? Obviously you've got the spec here, but has any work been done to implement this?
I see two other issues asking for similar stuff:
Also, there was a (discarded?) PR to add a split button in the past.
So stepping back a moment, two questions:
For @KingOfTac what's the status of this menu button? Has code been written yet?
For @chrisdholt, I'm hesitant to contribute here for fear my work would be discarded. Would FAST welcome a MenuButton, SplitButton, and NavBar? Or is this something you guys don't want, or would rather build yourselves?
This spec is still rather new. It needs more work and there are still a few unknowns around whether or not to compose existing components into this one or not. I have been working on a new version of the spec that I hope we'll be able to start implementing.
Another reason why there hasn't been much new component work is that most of the team has been focused on the next major release of FAST which is turning out to be one of the largest with many new features and breaking changes. For this reason I have been hesitant to start implementation until vNext is closer to release in order to reduce refactoring due to breaking changes in fast-element and fast-foundation.
Ah, interesting indeed. Well, I'm beginning to question whether I should wait to contribute then. Thanks for the heads up.
@chrisdholt want to weigh in? Would it be wise to wait to contribute until FAST vNext is released?
Hi, I'm interested in building a split button and dropdown button components for FAST. What's the status of this project? Obviously you've got the spec here, but has any work been done to implement this?
I see two other issues asking for similar stuff:
It appears there was a (discarded?) PR to add a split button in the past.
This all makes me a bit hesitant to contribute here
Well, first - we'd welcome a contribution here. Specific to the split button work, the hope was to bring that into a more well rounded menu button approach, perhaps via a slot (noted here: https://github.com/microsoft/fast/pull/4083#issuecomment-1030250715) because it lends itself better to our goals. The biggest concern with serving both menu button (as represented in this issue) and split button are that our primary method of approach is composition. A year or two ago there was a good amount of discussion around split buttons and their patterns - one big thing that landed was that they are two tab stops. They are both buttons, one being a normal button and one following the menu button pattern (still a button). So when exploring split button, I tend to ask how those things "compose", considering this is two buttons and we value composition, the most intuitive template for a composable split button is either one or two slots and the class itself likely doesn't need any logic because the element just organizes slotted content. Given that context, it was proposed in the spec review that perhaps split button as a construct is better served by being slotted in with a menu button. This would allow slotting the second via the light DOM for full control, etc. We decided we wanted to explore that route rather than deliver something that was a generic wrapper for buttons - Brook was going to pick that back up, but he ended up moving on to a different project and the need for that component fell off (until now). There is more to that backstory, but hopefully that gives at least some insight into why that was closed and nothing materialized from it.
I think the key here is that we'd love a contribution to FAST for any number of things component-wise and we certainly don't need to be the ones to build it ourselves. With that said though, I think any contribution directly to fast-foundation for instance needs to go through some kind of spec review to ensure that we're aligned in our approach, goals are being met, etc. We'd happily take a navbar, or navigation menu (we don't have that...basically a list w/ anchors), or other things - but I think the expectation for it bubbling into foundation is that it's aligned with our current approach and values - hopefully that makes sense. An example would be - if your concept for a menu is to forgo slots and composition in favor of taking in item data from an array; that's certainly doable but it doesn't align with our current approach of "composition first". We'll gladly take contributions (and we do quite often), but in order for us to provide consistent experience we need to ensure that the approach is consistent with our goals. With that said, if you're up for it, we'd welcome a contribution and we're happy to help work through certain nuances. Additionally, perhaps you have an idea for how we can approach composition while providing a great DX out of the box - we'd love to hear that and see how our approach might evolve.
Ah, interesting indeed. Well, I'm beginning to question whether I should wait to contribute then. Thanks for the heads up.
@chrisdholt want to weigh in? Would it be wise to wait to contribute until FAST vNext is released?
Eh....I say no :). It depends though, I know that could be a bit chaotic.
We're exploring a few things that I think are really exciting right now, but due to gaps I haven't put together an RFC on the direction. Even then, I don't think it would impact the work you'd be contributing in super significant ways...perhaps some light refactoring but I don't see it significantly changing approaches for instance.
@EisenbergEffect may have thoughts here too but in terms of creating new components, I think most of the breaks that would be most painful have already been made on the FAST Element side of things.
In terms of component building, I think you can use the v2 stuff in master with the v1 documentation. There aren't really breaking changes in how components are built and we've made the primary organizational changes in foundation. So, I think you're good to go.
We'd happily take a navbar, or navigation menu (we don't have that...basically a list w/ anchors), or other things - but I think the expectation for it bubbling into foundation is that it's aligned with our current approach and values - hopefully that makes sense. An example would be - if your concept for a menu is to forgo slots and composition in favor of taking in item data from an array; that's certainly doable but it doesn't align with our current approach of "composition first".
Alright, that does make sense.
I'd be doing this contribution as part of Microsoft's Fix / Hack / Learn (FHL) week, meaning I have limited time to do this. Probably not enough time to build a well-rounded spec and component that conforms to W3C specs, but maybe enough to get started.
If anything, I could start a spec and create a proof-of-concept component(s) during FHL week, then get feedback from you all and iterate on that in my spare time.
We'd happily take a navbar, or navigation menu (we don't have that...basically a list w/ anchors), or other things - but I think the expectation for it bubbling into foundation is that it's aligned with our current approach and values - hopefully that makes sense. An example would be - if your concept for a menu is to forgo slots and composition in favor of taking in item data from an array; that's certainly doable but it doesn't align with our current approach of "composition first".
Alright, that does make sense.
I'd be doing this contribution as part of Microsoft's Fix / Hack / Learn (FHL) week, meaning I have limited time to do this. Probably not enough time to build a well-rounded spec and component that conforms to W3C specs, but maybe enough to get started.
If anything, I could start a spec and create a proof-of-concept component(s) during FHL week, then get feedback from you all and iterate on that in my spare time.
Sounds good - and it doesn't have to be exhaustive. I'd provide as much content as needed and no more - we are pretty open to be iterative, etc. The big thing we're looking for is to identify any gaps in assumption or understanding and provide feedback up front to avoid someone going in an opposite direction or one that misses a few gotchas, requirements, etc.
Also, for some context here, the reason I'm interested in contributing these components is because I work on apps.microsoft.com, and we're looking to rewrite our front-end using web components. It's currently in React + old FAST (before Web Components!), but we're considering migrating to newer tech, and one option is modern FAST.
Looking at modern FAST and doing some quick tech spikes, I noticed it was missing some components we need, such as nav bar with collapsible menus on mobile, split buttons, and menu buttons. Hence why I'm interested in contributing. 🙂
Contributions welcome - rising tide raises all ships and whatnot :)
@chrisdholt @EisenbergEffect @JudahGabriel
New MenuButton spec that explores supporting split buttons. Per comments above, this merely positions the buttons and does not incorporate logic specific to split buttons.
Some basic exploratory examples here. https://stackblitz.com/edit/typescript-zg9qwx?file=index.html
MenuButton V2
Overview
As defined by the W3C:
A menu button is a button that opens a menu. It is often styled as a typical push button with a downward pointing arrow or triangle to hint that activating the button will display a menu.
Background
A common request in the FAST community is how to make dropdowns, either for action menus or navigation. It is also a very common pattern amongst UI libraries, and while it is something that can produced using a combination of components that already exist in FAST it still a difficult pattern to implement correctly when accessibility becomes involved.
Use Cases
- Jon wants to find a specific service offered by a company, so he clicks on the services menu button in the navbar to see a menu of all the services the company has to offer.
- Jane wants to learn more about her favorite brand, so she clicks on the company menu button to see a menu of links to the brand's contact, about us, and careers pages.
- Joe is writing a blog post using a wysiwyg editor and clicks on a menu button in the editor's toolbar to see a list of formatting actions available.
Features
-
Expanded/Collapsed Indicator: An indicator (usually down/up chevrons) that visually indicates the expanded/collapsed state of the menu, as well as indicating that the button opens a menu.
-
MenuPosition: Provides a way to set the position of the menu relative to the reference button.
-
SplitButton compisition via start/end slots When a button is slotted into either the start or end slot or both, the menu button becomes a split button.
Risks and Challenges
- This version composes
Menuwhich reduces the amount of logic for focus and keyboard navigation, however in order to fully align with the ARIA patterns forMenuButtonsMenuwill need to be updated to support a roving tabindex. - Typically, in design systems that use rounded corners for buttons, only the outer corners of a split button are rounded. Styling for these scenarios can be challenging when the buttons each live in different slots and/or some in light-dom and others in shadow-dom. One solution to this challenge is to watch for changes on the start/end slots for slotted button elements and add a class and/or part attribute to the internal container around the slots and internal button so that the buttons can be styled properly.
Prior Art/Examples
- ARIA APG Navigation MenuButton
- Stripe.com's mega menu
- WinUI's Menu Flyout
- Adobe Spectrum's Action Menu
- Bootstrap's Split Button
- Adobe Spectrum's Split Button
design
API
Component Name:
fast-menu-button
Attributes & Properties:
menu-position- enum that contains all of the valid positions for the menu. Defaults tobottom-start;expanded- boolean that indicates whether or not the menu button is in its expanded state. This attribute is not used for AT as the internalButtonelement has thearia-expandedattribute. This attribute is meant to enable easier ergonomics for styling theMenuButtonvia light-dom based on its current state.auto-update- boolean to enable auto updating the menu's position as theMenuButtonmoves around on the page.
Events
expand- fires when the menu is expanded.collapse- fires when the menu is collapsed.an alternative worth exploring is a single
expanded-changeevent that fires in both scenarios. This aligns better with the custom events spec where custom event names need a hyphen in them, as well as aligning with other Foundation component's events.
Anatomy
Notes: When the host element gets connected to the DOM, it will need to setup a click handler on the document in order to trigger the light dismiss of the menu.
- Some design systems add an arrow element to the menu that points at the button element. This can either be handled through component anatomy, or if a library like floating-ui is used for the positioning, its arrow middleware can be used.
MenuButtonOptions
collapsedIcon- Auther definable icon for the collapsed state.expandedIcon- Auther definable icon for the expanded state.
<div part="control">
<slot name="start"></slot>
<fast-button
aria-haspopup="true"
aria-expanded="{expanded || null}"
>
<slot></slot>
{
options
? expanded
? options.expandedIcon
: options.collapsedIcon
: ''
}
</fast-button>
<slot name="end"></slot>
</div>
<fast-menu
role="menu"
part="menu"
>
<slot name="item">
Menu items / dividers and other presentation content go here.
Anything that is not `role=menuitem` is ignored by keyboard navigation
</slot>
</fast-menu>
Slot Names
Notes: I'm on the fence on whether the default slot should be the internal button's content or the menu items. On the one hand you don't need to wrap the button's text with an element to slot it, but then you need to specify
slot="item"on every item which becomes tedious and adds clutter to the markup.
default- use the default slot for the button content.start- can be used for icons and text. If another button is slotted here then theMenuButtonturns into aSplitButton.end- can be used for icons and text. If another button is slotted here then theMenuButtonturns into aSplitButton.item- The menu items.
CSS Parts
control- The container around the button and start/end slots.menu- The menu element.
Implementation
States
expanded- If the menu is visible
Accessibility
- The MenuButton should align to the patterns provided by the W3C: https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/
- When additional buttons are slotted, the
MenuButtontransitions into a split button. In this scenario additional tab stops need to be handled for the extra button(s).
Globalization
The MenuButton will need to reverse the start and end slots, as well as the indicator for users in an RTL setting. The menu positions for start and end will also need to reverse.
Dependencies
-
Potentially floating-ui, if FAST ends up moving in that direction
-
If floating-ui is not used, then
AnchoredRegionor another utility ifAnchoredRegionis deprecated. -
Menu -
Button- There is an accessibility concern with reusing
ButtoninMenuButton. While prototyping withButtonNVDA announces that the menu button is clickable twice, while using a native button does not have this behavior. I suspect this has something to do withButtondelegating focus, but haven't thoroughly tested this yet. Settingrole="none"on theButtoncould be a temporary workaround until a better solution is found.
- There is an accessibility concern with reusing
Appendix
Resources
W3C Spec for Menu, Menubar, and MenuButton W3C Accessible MenuButton Example