Hi,

Problem/Motivation

When mapping a menu (e.g. main or footer) to a Links-type prop in a UI Patterns pattern, Drupal allows the use of special tokens in the menu link URLs:

  • <front> : works as expected
  • <nolink> : works as expected
  • <button>: the link is completely removed from the output

This behavior is problematic, as Drupal's admin UI explicitly allows adding button-type links to menus. However, the UI Patterns module does not currently support this case properly, which prevents the button from being rendered.

Steps to reproduce

  • Drupal core version: 10.4
  • UI Patterns module version: 2.x
  • Menu items are MenuLinkContent entities.

Links are mapped to a prop of type Links, using a schema defined by LinksPropType, which expects the following structure:

[
  'type' => 'object',
  'properties' => [
    'title' => ['type' => 'string'],
    'url' => ['$ref' => 'ui-patterns://url'],
    'attributes' => ['$ref' => 'ui-patterns://attributes'],
    'link_attributes' => ['$ref' => 'ui-patterns://attributes'],
    'below' => [
      'type' => 'array',
      'items' => [
        'type' => 'object',
      ],
    ],
  ],
]

However, when a link uses the <button> token in the Link field, the preprocessing handled by the normalize() method in UrlPropType returns an empty string ('') for the URL.

As a result, the schema validation for LinksPropType either rejects or silently ignores the item, because it no longer matches the expected format for a valid link.

This use case can be essential, particularly for accessibility reasons, such as:

  • A parent menu link used only to trigger expand/collapse behavior
  • A menu item that opens a consent modal
  • A purely interactive navigation element that doesn’t need an actual URL

Proposed resolution

Menu items with a <button> URL should be included in the Links prop, with an identifiable format for example, a type: 'button' field or a boolean is_button allowing the front-end integrator to distinguish and render the element appropriately (e.g., using a <button> instead of an <a> tag).

Marc

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

martygraphie created an issue. See original summary.

martygraphie’s picture

Status: Active » Needs review

Hi,
This patch adds support for the is_button indicator on link (links) objects.
The goal is to allow templates especially within navigation components to easily detect when a <button> type route should be rendered as a <button> HTML element.
It might not be the most elegant solution, but it's simple and functional.
Example usage in a TWIG template of component:

{% if item.is_button %}
  <button{{ item.link_attributes }}>
    {{ item.title }}
  </button>
{% endif %}

Marc

pdureau’s picture

Assigned: Unassigned » pdureau

I am not very comfortable with this proposal. Not because of Marc's work. It seems he did a great job. But because of some weird stuff on Drupal Core side.

I will take the time to review it properly.

just_like_good_vibes’s picture

Assigned: pdureau » just_like_good_vibes
Status: Needs review » Needs work

hello,
yes we should support those special routes in some ways.

to tackle this, we need to carefully normalizeUrls and add new automatic tests to verify the support.
i will continue the job.

spryah made their first commit to this issue’s fork.

anwoon made their first commit to this issue’s fork.

just_like_good_vibes’s picture

Assigned: just_like_good_vibes » pdureau
Status: Needs work » Needs review

Hello, and sorry for taking so much time to come back to that issue.
i see that it gains some recent interest.
i just gave some time and effort on this use case to try to propose something.

but first, I used some basic questions to help me evaluates the proposition :
- is route:<button> a real Drupal core concept? Yes, since Drupal 9.0 (Change record "Menu links can be buttons", from core issue #2999549: Allow button tag in LinkGenerator for better accessibility)
- Is the use case legitimate? Yes. Menu items that toggle mega-menus/dropdowns must be for accessibility (a link that navigates nowhere is an a11y failure). Editors model this with a route: menu item in Drupal.
- Is the reported problem real? Yes, but let me complete the explanation. LinksPropType::normalizeUrl() is already unsetting the url for the case route:<button> (same behavior as route:<nolink> ), but no marker is sets. After normalization, impossible to know if it was the route or the route . One can just remark that url is empty. button-ness is lost...

about the is_button addition :
- links prop type is actually a collection of navigation items, from the UI perspective. every key is a pure UI concept.
- the addition of is_button is not a backend information (as would be the introduction of a property route_name for example) : it is an UI concept, because a navigation item in HTML and in design systems usually fall back into one of the three following cases : an anchor (for navigation), a button (to trigger a behavior), or plain text (for labels and headings).
- the current schema can encode: url present = anchor; url absent = text. Only two of the three states described above. The button state is simply unrepresentable and is_button is the minimal addition to encode that third state.
- introducting is_button is an addition and it provides some advantages : url stays unset in the button case (backward compatible, old templates behaves and degrades exactly as today), new templates can opt in with {% if item.is_button %}

i re-opened a fresh branch and pushed some codes, following the "is_button" idea.

@pdureau would you review?

pdureau’s picture

Assigned: pdureau » just_like_good_vibes
Status: Needs review » Reviewed & tested by the community

the addition of is_button is not a backend information (as would be the introduction of a property route_name for example) : it is an UI concept, because a navigation item in HTML and in design systems usually fall back into one of the three following cases : an anchor (for navigation), a button (to trigger a behavior), or plain text (for labels and headings).

That's right and that's why I was uncomfortable with this change

The last proposal is adding is_button property to link items, is it orthogonal with the url property?

is_button FALSE is_button TRUE
filled url just a normal link I guess it is up to the component author to resolve this ambiguity
empty url a plain text a normal button

I move to RTBC anyway, because the change is simple and the feature is expected, but I am available to chit-chat if needed.

just_like_good_vibes’s picture

@pdureau, you are totally right about the fact that, if both url is filled and is_button is true, then it is up to the component author to resolve this ambiguity and decide how to render the corresponding navigation item.
thank you for reviewing.

just_like_good_vibes’s picture

Assigned: just_like_good_vibes » Unassigned
Status: Reviewed & tested by the community » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.