feat(button): add neutral variant to the button directive - #3853
Merged
Conversation
The button directive offered two variants and both were blue. A `secondary` button next to a `primary` one competes for attention. A cluster of peer links had no correct option, because every button claimed to be a call to action. Add `:type: neutral`. It keeps the geometry of the existing variants and changes only the color and the weight. All values come from theme.css, so the change introduces no new tokens. The border uses `grey-70`. That is the lightest grey token that clears the 3:1 non-text contrast ratio in WCAG 1.4.11, at 3.31:1 against white. `grey-60` fails at 2.67:1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
theletterf
approved these changes
Aug 13, 2026
Mpdreamz
approved these changes
Aug 13, 2026
Member
|
@itsalexcm merged this one in but your word is final. |
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
#3853 added `:type: neutral` to the {button} directive, a monochrome outline for clusters where blue competes with the primary action. That is exactly what the hero needs: three actions of equal rank, none of which should claim to be the call to action. #3853 also flagged that this stack used border colours failing the 3:1 non-text contrast ratio. The eyebrow pill moves from grey-20, which reads at 1.32:1, to grey-70 at 3.31:1. The border is what identifies that control, so it has to clear the threshold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mpdreamz
pushed a commit
that referenced
this pull request
Aug 14, 2026
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Mpdreamz
added a commit
that referenced
this pull request
Aug 14, 2026
* Add hub page layout and the {hero} directive
Adds the first two pieces of the hub page mechanism, rebuilt on the current
navigation implementation. The prototype branch is merged with nav-v2, which is
out of scope, so nothing from nav-v2 ports.
Adds `layout: hub`, which drops the right-rail table of contents and the
previous/next navigation, keeps the left sidebar, and gives the body the full
content column. Page-title detection gains two fallbacks, so a page composed
only of directives still resolves a title: an H1 nested in a directive, then the
first {hero} directive's :title: option.
Adds {hero}, the identity band. It carries a product icon, the page title, a
description, and up to three optional actions.
Promotes the prototype's hub-only link validator to a shared
DirectiveLinkValidator. Directive links never pass through Markdig's inline link
parser, so every directive that takes a URL had invented its own handling.
{page-card} moves onto the shared class and its hand-rolled copy goes. It keeps
its previous contract: relative links resolve against the source file, and no
file-existence check runs, because page-card links can target generated pages
that have no markdown file on disk.
Guards DirectiveBlockParser.TryContinue so an ancestor directive stops consuming
option lines once it has opened a nested directive child. Without the guard the
ancestor swallows every descendant's options and the last one wins. This affects
every nested directive, so it lands with regression tests over tab sets,
steppers, and dropdowns.
Adds LLM markdown and plain-text renderers for {hero}. A hub page has no body
prose, so without them it exports as an empty shell. The plain-text renderer
feeds the search index and emits the hero title and description only. Hub pages
answer generic "<product> docs" queries. Indexing section and card titles would
let a hub compete with the pages it links to on specific queries.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Fix hub page regressions found in the rendered output
Three defects, all introduced in this branch rather than carried from the
prototype.
Suppress the page-level H1 and the applies-to badge on a hub page. The prototype
guards both in Index.cshtml and that guard was missed, so every hub page rendered
the page title twice: once from Index.cshtml, once from {hero}. This also
delivers the applies-to suppression that #1386 records but the code did not do.
Drop the markdown-content class from the hub article. The class was added to
reach the JS hooks, but those select on the markdown-content id, not the class.
typography.css is unlayered and hub.css sits in @layer components, so unlayered
rules win regardless of specificity. The class made typography.css restyle the
hero eyebrow as a blue underlined link and skew the icon alignment against the
heading. The id stays, so hljs, smooth-scroll and toc-nav keep working.
Give hero actions the same link treatment as inline links. An external action now
opens in a new tab with rel="noopener noreferrer" and no preload, and an anchor
action no longer preloads. Only an internal link preloads.
Fix an empty href for "/index.md". Stripping the whole "/index.md" segment left
an empty string rather than the site root.
The example page gains three actions, one internal and two external, to
demonstrate both forms.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Align the hero product mark with the text column
The mark sat about 9px right of the eyebrow and the description below it. Two
insets stacked. Each icon's viewBox carried transparent padding around the glyph,
which cost 5px at the rendered size. The 48px chip then centred the 40px SVG
inside itself, which cost another 4px.
Crops every icon's viewBox to its glyph, so no transparent padding is left to
absorb, and lets the box hug the mark instead of forcing it into a fixed square.
Width now follows from the glyph's own aspect ratio.
The letter fallback keeps the chip. It has no glyph to align, and the chip's own
edge already lines up with the text column.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Make the example a docs-builder hub, and keep cross-links out of new tabs
The example page was an Elasticsearch hub whose links had all been repointed at
docs-builder pages, leaving an Elasticsearch name, icon, hero and headings on top
of docs-builder content. It is now a docs-builder hub throughout, renamed to
docs/examples/products/docs-builder.md.
Its hero demonstrates all three link forms: a site-absolute path, a docs-content
cross-link, and an in-page anchor. The unknown `docs-builder` icon key also
exercises the single-letter fallback.
Fixes a defect the cross-link uncovered. A cross-link resolves to a full URL, so
the hero treated it as external and opened it in a new tab. Inline links make the
opposite call, and deliberately so: a cross-link still points at documentation
this site serves. DirectiveLinkValidator now records the URLs it resolved from a
cross-link scheme, and the view model consults that rather than guessing from the
scheme.
Adds doesNotContainHtml to the authoring assertions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Apply review feedback to the hero and hub layout
Martijn's review on #3825 and theletterf's questions.
- The hero no longer adds its own horizontal padding. The content column
supplies the gutter, so the hero aligned with the sections below it only
above the shared max-width. Below it the hero sat 24px further in.
- Hero type moves onto the site's scale. The root font size is 14px, so
values written as 16px-root arithmetic rendered around 12% small. The
description in particular used a size found nowhere else on the site.
- The three hero actions carry equal weight and take the secondary button
treatment from the {button} directive, rather than a bespoke hero button.
The option names set order, not weight.
- A page with `layout: hub` and no {hero} now fails the build. The layout
removes the page H1, so without a hero the page has no title at all and
falls back to its file path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Raise the hero eyebrow to body size
Martijn's point about tiny fonts applies to every piece of hub text, not
only the ones in his screenshot. The eyebrow is a reading affordance, so it
takes the body size.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Follow the site's rule for arrows on links
The site puts an arrow on two kinds of link: one that sends the reader
onward out of a section, such as "View all reference docs" on the landing
page, and a row that is itself the target, such as {page-card} or the
previous and next footer. It marks no ordinary link and no button.
The hero followed neither. Its actions are buttons, so they carried an
arrow the site gives no button, and only when the target was an in-page
anchor. Both are gone.
The eyebrow keeps its arrow, because it sends the reader onward to the docs
home. It now uses the site's glyph and hover slide through one shared
`.hub-arrow` class, rather than a second arrow drawn by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Put the hero actions on the neutral button variant
#3853 added `:type: neutral` to the {button} directive, a monochrome
outline for clusters where blue competes with the primary action. That is
exactly what the hero needs: three actions of equal rank, none of which
should claim to be the call to action.
#3853 also flagged that this stack used border colours failing the 3:1
non-text contrast ratio. The eyebrow pill moves from grey-20, which reads
at 1.32:1, to grey-70 at 3.31:1. The border is what identifies that
control, so it has to clear the threshold.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Add Logstash icon to hub ProductIcons (#3843)
Hub heroes fall back to initials when a product key is missing from the
inline SVG map. Logstash hubs need the product mark.
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Martijn Laarman <Mpdreamz@gmail.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Brandon Morelli <brandon.morelli@elastic.co>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Martijn Laarman <Mpdreamz@gmail.com>
yetanothertw
pushed a commit
that referenced
this pull request
Aug 14, 2026
* Add hub page layout and the {hero} directive
Adds the first two pieces of the hub page mechanism, rebuilt on the current
navigation implementation. The prototype branch is merged with nav-v2, which is
out of scope, so nothing from nav-v2 ports.
Adds `layout: hub`, which drops the right-rail table of contents and the
previous/next navigation, keeps the left sidebar, and gives the body the full
content column. Page-title detection gains two fallbacks, so a page composed
only of directives still resolves a title: an H1 nested in a directive, then the
first {hero} directive's :title: option.
Adds {hero}, the identity band. It carries a product icon, the page title, a
description, and up to three optional actions.
Promotes the prototype's hub-only link validator to a shared
DirectiveLinkValidator. Directive links never pass through Markdig's inline link
parser, so every directive that takes a URL had invented its own handling.
{page-card} moves onto the shared class and its hand-rolled copy goes. It keeps
its previous contract: relative links resolve against the source file, and no
file-existence check runs, because page-card links can target generated pages
that have no markdown file on disk.
Guards DirectiveBlockParser.TryContinue so an ancestor directive stops consuming
option lines once it has opened a nested directive child. Without the guard the
ancestor swallows every descendant's options and the last one wins. This affects
every nested directive, so it lands with regression tests over tab sets,
steppers, and dropdowns.
Adds LLM markdown and plain-text renderers for {hero}. A hub page has no body
prose, so without them it exports as an empty shell. The plain-text renderer
feeds the search index and emits the hero title and description only. Hub pages
answer generic "<product> docs" queries. Indexing section and card titles would
let a hub compete with the pages it links to on specific queries.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Fix hub page regressions found in the rendered output
Three defects, all introduced in this branch rather than carried from the
prototype.
Suppress the page-level H1 and the applies-to badge on a hub page. The prototype
guards both in Index.cshtml and that guard was missed, so every hub page rendered
the page title twice: once from Index.cshtml, once from {hero}. This also
delivers the applies-to suppression that #1386 records but the code did not do.
Drop the markdown-content class from the hub article. The class was added to
reach the JS hooks, but those select on the markdown-content id, not the class.
typography.css is unlayered and hub.css sits in @layer components, so unlayered
rules win regardless of specificity. The class made typography.css restyle the
hero eyebrow as a blue underlined link and skew the icon alignment against the
heading. The id stays, so hljs, smooth-scroll and toc-nav keep working.
Give hero actions the same link treatment as inline links. An external action now
opens in a new tab with rel="noopener noreferrer" and no preload, and an anchor
action no longer preloads. Only an internal link preloads.
Fix an empty href for "/index.md". Stripping the whole "/index.md" segment left
an empty string rather than the site root.
The example page gains three actions, one internal and two external, to
demonstrate both forms.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Align the hero product mark with the text column
The mark sat about 9px right of the eyebrow and the description below it. Two
insets stacked. Each icon's viewBox carried transparent padding around the glyph,
which cost 5px at the rendered size. The 48px chip then centred the 40px SVG
inside itself, which cost another 4px.
Crops every icon's viewBox to its glyph, so no transparent padding is left to
absorb, and lets the box hug the mark instead of forcing it into a fixed square.
Width now follows from the glyph's own aspect ratio.
The letter fallback keeps the chip. It has no glyph to align, and the chip's own
edge already lines up with the text column.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Make the example a docs-builder hub, and keep cross-links out of new tabs
The example page was an Elasticsearch hub whose links had all been repointed at
docs-builder pages, leaving an Elasticsearch name, icon, hero and headings on top
of docs-builder content. It is now a docs-builder hub throughout, renamed to
docs/examples/products/docs-builder.md.
Its hero demonstrates all three link forms: a site-absolute path, a docs-content
cross-link, and an in-page anchor. The unknown `docs-builder` icon key also
exercises the single-letter fallback.
Fixes a defect the cross-link uncovered. A cross-link resolves to a full URL, so
the hero treated it as external and opened it in a new tab. Inline links make the
opposite call, and deliberately so: a cross-link still points at documentation
this site serves. DirectiveLinkValidator now records the URLs it resolved from a
cross-link scheme, and the view model consults that rather than guessing from the
scheme.
Adds doesNotContainHtml to the authoring assertions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Apply review feedback to the hero and hub layout
Martijn's review on #3825 and theletterf's questions.
- The hero no longer adds its own horizontal padding. The content column
supplies the gutter, so the hero aligned with the sections below it only
above the shared max-width. Below it the hero sat 24px further in.
- Hero type moves onto the site's scale. The root font size is 14px, so
values written as 16px-root arithmetic rendered around 12% small. The
description in particular used a size found nowhere else on the site.
- The three hero actions carry equal weight and take the secondary button
treatment from the {button} directive, rather than a bespoke hero button.
The option names set order, not weight.
- A page with `layout: hub` and no {hero} now fails the build. The layout
removes the page H1, so without a hero the page has no title at all and
falls back to its file path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Raise the hero eyebrow to body size
Martijn's point about tiny fonts applies to every piece of hub text, not
only the ones in his screenshot. The eyebrow is a reading affordance, so it
takes the body size.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Follow the site's rule for arrows on links
The site puts an arrow on two kinds of link: one that sends the reader
onward out of a section, such as "View all reference docs" on the landing
page, and a row that is itself the target, such as {page-card} or the
previous and next footer. It marks no ordinary link and no button.
The hero followed neither. Its actions are buttons, so they carried an
arrow the site gives no button, and only when the target was an in-page
anchor. Both are gone.
The eyebrow keeps its arrow, because it sends the reader onward to the docs
home. It now uses the site's glyph and hover slide through one shared
`.hub-arrow` class, rather than a second arrow drawn by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Put the hero actions on the neutral button variant
#3853 added `:type: neutral` to the {button} directive, a monochrome
outline for clusters where blue competes with the primary action. That is
exactly what the hero needs: three actions of equal rank, none of which
should claim to be the call to action.
#3853 also flagged that this stack used border colours failing the 3:1
non-text contrast ratio. The eyebrow pill moves from grey-20, which reads
at 1.32:1, to grey-70 at 3.31:1. The border is what identifies that
control, so it has to clear the threshold.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Add Logstash icon to hub ProductIcons (#3843)
Hub heroes fall back to initials when a product key is missing from the
inline SVG map. Logstash hubs need the product mark.
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Martijn Laarman <Mpdreamz@gmail.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Brandon Morelli <brandon.morelli@elastic.co>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Martijn Laarman <Mpdreamz@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to the hub pages thread. Martijn asked for a third button type exposed on the button directive, and Fabri described it as a monochrome secondary button.
Problem
The
{button}directive offers two variants and both are blue.primaryis a filled Elastic blue,secondaryis an Elastic blue outline.When the two sit next to each other, the secondary button competes with the primary for attention. A cluster of peer links has no correct option at all, because every button claims to be a call to action. That is what blocks the hub hero, where several buttons need equal rank.
What this adds
:type: neutral. It keeps the geometry of the existing variants and changes only the color and the weight.#868e9agrey-70border#1c1e23ink-darkfont-medium#1c1e23ink-dark#f6f9fcgrey-10#85b7ffblue-elastic-50Every value comes from
theme.css. The change introduces no new tokens.Height, padding, radius, and font size match
primaryandsecondaryexactly, so a neutral button lines up with its neighbors inside abutton-group. The focus ring stays Elastic blue, so keyboard focus reads the same across all three variants.Two deliberate divergences, please review
These are the parts I'd most like a second opinion on, because they depart from
_LandingPage.cshtml, which is where the existing two styles came from.secondary. A 2px grey border carried too much visual weight for a variant whose job is to recede.Both keep
box-sizing: border-box, so the outer geometry does not change and mixed-variant groups still align.Accessibility
WCAG 1.4.11 asks for 3:1 contrast on the boundary that identifies a control.
grey-70measures 3.31:1 against white and is the lightest grey token that clears it.grey-60fails at 2.67:1. Text contrast measures 16.68:1.Worth flagging for the hub pages stack: the current proof of concept uses
grey-20(1.32:1) andgrey-30(1.63:1) borders. Both fail that threshold and need lifting when they adopt this variant.Out of scope
ink-darkbordered lead action. The hero can pair oneprimarywith severalneutralbuttons instead. Worth revisiting if the all-monochrome hero wins.:tone:property. It doubles the style surface for combinations nobody has asked for yet.Testing
ButtonTests.cscovering the type, the:variant:alias, rendering inside abutton-group, and no diagnostics.dotnet test tests/Elastic.Markdown.Tests/ --filter "FullyQualifiedName~Button"passes 72 tests.npm run buildinsrc/Elastic.Documentation.Siteconfirms every Tailwind utility resolves to a real token.docs/syntax/buttons.mddocuments the variant, so the rendered preview of/syntax/buttonsexercises it.🤖 Generated with Claude Code