From 90b3f3f2ddfbe7f09e07ea70ea4de3592c1f15ea Mon Sep 17 00:00:00 2001 From: Cory Rylan Date: Fri, 4 Sep 2026 17:29:04 -0500 Subject: [PATCH 1/3] fix(lint): no-excessive-primary-actions rule to handle popover elements - Added tests to ensure emphasis buttons inside popover elements are ignored. - Implemented logic to count emphasis buttons outside popover elements, ensuring proper validation. - Introduced a helper function to check for popover ancestors in the DOM structure. This update improves the linting accuracy for emphasis button usage in complex UI structures. Signed-off-by: Cory Rylan --- .../no-excessive-primary-actions.test.ts | 45 +++++++++++++++++++ .../rules/no-excessive-primary-actions.ts | 21 ++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/projects/lint/src/eslint/rules/no-excessive-primary-actions.test.ts b/projects/lint/src/eslint/rules/no-excessive-primary-actions.test.ts index a7415afa8..a688f7044 100644 --- a/projects/lint/src/eslint/rules/no-excessive-primary-actions.test.ts +++ b/projects/lint/src/eslint/rules/no-excessive-primary-actions.test.ts @@ -80,6 +80,51 @@ describe('noExcessivePrimaryActions', () => { }); }); + it('should ignore emphasis buttons inside popover elements', () => { + tester.run('popover emphasis buttons', rule, { + valid: [ + `Page action one + + + Dialog action one + + + + + Dialog action two + + + Page action two`, + `Drawer action + Dropdown action + Notification action + Group action + Loader action + Toast action + Toggletip action + Tooltip action` + ], + invalid: [] + }); + }); + + it('should continue counting emphasis buttons outside popover elements', () => { + tester.run('page emphasis buttons around popovers', rule, { + valid: [], + invalid: [ + { + code: `Page action one + + Dialog action + + Page action two + Page action three`, + errors: [error] + } + ] + }); + }); + it('should count separate tagged templates independently', () => { const javascriptTester = new RuleTester({ languageOptions: { diff --git a/projects/lint/src/eslint/rules/no-excessive-primary-actions.ts b/projects/lint/src/eslint/rules/no-excessive-primary-actions.ts index 1dd17be2c..50278f5e5 100644 --- a/projects/lint/src/eslint/rules/no-excessive-primary-actions.ts +++ b/projects/lint/src/eslint/rules/no-excessive-primary-actions.ts @@ -4,10 +4,29 @@ import type { Rule } from 'eslint'; import { createVisitors } from '@html-eslint/eslint-plugin/lib/rules/utils/visitors.js'; import { findAttr } from '@html-eslint/eslint-plugin/lib/rules/utils/node.js'; +import { elements } from '../internals/metadata.js'; import type { HtmlTagNode } from '../rule-types.js'; declare const __ELEMENTS_PAGES_BASE_URL__: string; const MAX_EMPHASIS_BUTTONS = 2; +const POPOVER_ELEMENTS: ReadonlySet = new Set( + elements + .filter(element => element.manifest?.metadata?.behavior === 'popover') + .map(element => element.name.toLowerCase()) +); + +function hasPopoverAncestor(node: HtmlTagNode): boolean { + let current = node.parent; + + while (current) { + if (current.name && POPOVER_ELEMENTS.has(current.name.toLowerCase())) { + return true; + } + current = current.parent; + } + + return false; +} const rule = { meta: { @@ -32,7 +51,7 @@ const rule = { emphasisButtonCount = 0; }, Tag(node: HtmlTagNode) { - if (node.name.toLowerCase() !== 'nve-button') { + if (node.name.toLowerCase() !== 'nve-button' || hasPopoverAncestor(node)) { return; } From 2eef85cda1f7fc99f733367852763b0569427ddc Mon Sep 17 00:00:00 2001 From: Cory Rylan Date: Wed, 9 Sep 2026 10:55:11 -0500 Subject: [PATCH 2/3] feat(lint): support markdown linting Signed-off-by: Cory Rylan --- projects/lint/README.md | 2 ++ projects/lint/src/eslint/configs/html.ts | 2 +- projects/lint/src/eslint/index.test.ts | 34 +++++++++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/projects/lint/README.md b/projects/lint/README.md index 39a8c76b9..5245c6014 100644 --- a/projects/lint/README.md +++ b/projects/lint/README.md @@ -4,6 +4,8 @@ NVIDIA Design System and UI Agent Harness for AI/ML Factories, Robotics, and Aut The `@nvidia-elements/lint` package is a utility library that provides Elements-specific lint rules to enforce best practices and prevent common errors when using Elements. +The HTML configuration checks HTML in `src/**/*.html`, supported JavaScript and TypeScript templates, and Markdown files under `src/**/*.md`. Markdown linting includes rendered markup and HTML examples in fenced code blocks. + ## Getting Started ```shell diff --git a/projects/lint/src/eslint/configs/html.ts b/projects/lint/src/eslint/configs/html.ts index e2623b899..3ac660069 100644 --- a/projects/lint/src/eslint/configs/html.ts +++ b/projects/lint/src/eslint/configs/html.ts @@ -37,7 +37,7 @@ import noUnstyledTypography from '../rules/no-unstyled-typography.js'; import noTailwindClasses from '../rules/no-tailwind-classes.js'; import preferAriaLabelInCompactContainers from '../rules/prefer-aria-label-in-compact-containers.js'; -const source = ['src/**/*.html', 'src/**/*.js', 'src/**/*.ts', 'src/**/*.tsx']; +const source = ['src/**/*.html', 'src/**/*.js', 'src/**/*.md', 'src/**/*.ts', 'src/**/*.tsx']; const ignores = [ 'node_modules/', diff --git a/projects/lint/src/eslint/index.test.ts b/projects/lint/src/eslint/index.test.ts index 6fd4b3f71..e809a9e42 100644 --- a/projects/lint/src/eslint/index.test.ts +++ b/projects/lint/src/eslint/index.test.ts @@ -1,11 +1,43 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { ESLint } from 'eslint'; import { describe, expect, it } from 'vitest'; -import { VERSION } from './index.js'; +import { elementsHtmlConfig, VERSION } from './index.js'; describe('VERSION', () => { it('should export a VERSION const', () => { expect(VERSION).toBe('0.0.0'); }); }); + +describe('elementsHtmlConfig', () => { + it('should lint rendered and fenced HTML in Markdown files', async () => { + const eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: [elementsHtmlConfig] + }); + const markdown = `--- +title: Example +--- + + + +\`\`\`html + +\`\`\``; + + const [result] = await eslint.lintText(markdown, { filePath: 'src/example.md' }); + + expect(result.messages).toEqual([ + expect.objectContaining({ + ruleId: '@nvidia-elements/lint/no-unknown-tags', + line: 5 + }), + expect.objectContaining({ + ruleId: '@nvidia-elements/lint/no-unknown-tags', + line: 8 + }) + ]); + }); +}); From c359af56b3fe0a064c4f3a8ca7db6bd5106cee7f Mon Sep 17 00:00:00 2001 From: Cory Rylan Date: Wed, 9 Sep 2026 10:55:29 -0500 Subject: [PATCH 3/3] chore(docs): fix markdown lint errors Signed-off-by: Cory Rylan --- .../src/local/example-approved-domains.js | 2 +- projects/site/eslint.config.js | 27 ++++++++++++++ projects/site/package.json | 1 + .../site/src/docs/api-design/composition.md | 20 +++++++---- projects/site/src/docs/api-design/index.md | 16 ++++----- projects/site/src/docs/api-design/logs.md | 8 +++-- .../site/src/docs/api-design/packaging.md | 8 ++--- .../docs/api-design/properties-attributes.md | 14 ++++---- .../site/src/docs/api-design/registration.md | 4 +-- projects/site/src/docs/api-design/slots.md | 36 +++++++++---------- .../site/src/docs/api-design/stateless.md | 10 +++--- projects/site/src/docs/api-design/styles.md | 22 +++++++----- projects/site/src/docs/elements/control.md | 4 +-- .../docs/elements/data-grid/column-action.md | 2 +- .../elements/data-grid/column-alignment.md | 4 +-- .../elements/data-grid/display-settings.md | 22 ++++++------ .../docs/elements/data-grid/panel-detail.md | 2 +- .../src/docs/elements/data-grid/panel-grid.md | 27 +++++++++----- .../src/docs/elements/data-grid/row-action.md | 4 +-- .../src/docs/elements/data-grid/row-sort.md | 4 +++ projects/site/src/docs/elements/tabs.md | 2 +- projects/site/src/docs/elements/tooltip.md | 2 +- projects/site/src/docs/foundations/i18n.md | 3 +- .../site/src/docs/foundations/iconography.md | 4 +-- .../site/src/docs/foundations/layout/grid.md | 20 +++++++---- .../src/docs/foundations/layout/horizontal.md | 10 +++--- .../site/src/docs/foundations/layout/index.md | 32 +++++++++-------- .../layout/responsive/container.md | 16 ++++----- .../foundations/layout/responsive/index.md | 12 +++---- .../foundations/layout/responsive/viewport.md | 14 ++++---- .../src/docs/foundations/layout/vertical.md | 24 +++++++------ .../site/src/docs/integrations/angular.md | 2 +- projects/site/src/docs/lint/index.md | 2 ++ projects/site/src/docs/monaco/diff-editor.md | 4 +-- projects/site/src/docs/monaco/editor.md | 4 +-- projects/site/src/docs/patterns/index.md | 2 +- projects/site/src/docs/whats-new/05-2026.md | 9 ++++- 37 files changed, 240 insertions(+), 159 deletions(-) diff --git a/projects/internals/eslint/src/local/example-approved-domains.js b/projects/internals/eslint/src/local/example-approved-domains.js index ccf45f383..ed59feffa 100644 --- a/projects/internals/eslint/src/local/example-approved-domains.js +++ b/projects/internals/eslint/src/local/example-approved-domains.js @@ -1,6 +1,6 @@ import { findHtmlTemplate, getTemplateText, hasTag } from './example-helpers.js'; -const APPROVED_DOMAINS = ['nvidia.com', 'github.com']; +const APPROVED_DOMAINS = ['nvidia.com', 'github.com', 'huggingface.co']; const URL_ATTRIBUTES = ['href', 'src', 'srcset']; const URL_ATTRIBUTE_PATTERN = new RegExp(`\\b(${URL_ATTRIBUTES.join('|')})\\s*=\\s*("([^"]*)"|'([^']*)')`, 'gi'); diff --git a/projects/site/eslint.config.js b/projects/site/eslint.config.js index 1b45c4891..72a44ca06 100644 --- a/projects/site/eslint.config.js +++ b/projects/site/eslint.config.js @@ -11,6 +11,33 @@ export default [ ...browserTypescriptConfig, ...appConfig, ...jsonConfig, + { + // These examples intentionally demonstrate incomplete and hypothetical component APIs. + files: ['src/docs/api-design/**/*.md', 'src/docs/internal/guidelines/**/*.md'], + rules: { + '@nvidia-elements/lint/no-missing-control-label': 'off', + '@nvidia-elements/lint/no-missing-slotted-elements': 'off', + '@nvidia-elements/lint/no-unknown-tags': 'off' + } + }, + { + // These examples intentionally demonstrate deprecated APIs. + files: ['src/docs/about/migration.md'], + rules: { + '@nvidia-elements/lint/no-deprecated-tags': 'off', + '@nvidia-elements/lint/no-deprecated-attributes': 'off', + '@nvidia-elements/lint/no-deprecated-global-attributes': 'off', + '@nvidia-elements/lint/no-deprecated-popover-attributes': 'off', + '@nvidia-elements/lint/no-deprecated-icon-names': 'off', + '@nvidia-elements/lint/no-unexpected-attribute-value': 'off', + '@nvidia-elements/lint/no-unstyled-typography': 'off', + '@nvidia-elements/lint/no-missing-control-label': 'off', + '@nvidia-elements/lint/no-missing-slotted-elements': 'off', + '@nvidia-elements/lint/no-unexpected-global-attribute-value': 'off', + '@nvidia-elements/lint/no-restricted-container-full': 'off', + '@nvidia-elements/lint/no-unknown-tags': 'off' + } + }, { files: ['src/_11ty/**/*.js'], rules: { diff --git a/projects/site/package.json b/projects/site/package.json index 6112013fb..eb3de8bc1 100644 --- a/projects/site/package.json +++ b/projects/site/package.json @@ -201,6 +201,7 @@ "command": "eslint -c ./eslint.config.js --color --cache --cache-location .eslintcache/", "files": [ "src/**/*.js", + "src/**/*.md", "src/**/*.ts", "eslint.config.js" ], diff --git a/projects/site/src/docs/api-design/composition.md b/projects/site/src/docs/api-design/composition.md index 3fec91388..d9346df3c 100644 --- a/projects/site/src/docs/api-design/composition.md +++ b/projects/site/src/docs/api-design/composition.md @@ -16,7 +16,7 @@ Elements should default to using composition when possible. This approach is to ```html - button + button ``` @@ -36,7 +36,7 @@ Going further this runs into layout conflicts. If the icon needs to change posit ```html - button + button ``` @@ -64,6 +64,8 @@ Elements should provide reasonable defaults for better developer experience for The alert can internally provide the default icon style for the status in the system. But as above with the button, the alert element runs the risk of absorbing parts of the icon API. To mitigate this, use a documented named slot as the customization hook. + + ```html
@@ -80,12 +82,16 @@ The alert can internally provide the default icon style for the status in the sy ``` + + Slots can provide default content if the consumer supplies no content. Here the template sets an internal icon with a status icon that matches the status of the alert. If the consumer wants to customize the icon, they can project their own icon into the `icon` slot and override the default. This makes `icon` an explicit public slot API, while avoiding a series of icon-specific inherited attributes or properties on the alert. ## Semantic Obfuscation - anti-pattern When building composition based APIs the developer should push the semantics of the HTML up into the light DOM or the control of the consumer. In this example the card element embeds the h1 heading. This creates an incorrect DOM structure as only one given h1 can exist within the page. This also applies as the page structure should work down from h1-h6. + + {% dodont %} ```html @@ -100,15 +106,15 @@ When building composition based APIs the developer should push the semantics of -

Card Header

-

card content

+

Card Header

+

card content

``` ```html
-

+

@@ -118,12 +124,14 @@ When building composition based APIs the developer should push the semantics of
Card Header
-

card content

+

card content

``` {% enddodont %} + + While composition based APIs may be more verbose at times, they lower the API surface area to learn in the system and help ensure there is a singular way to use the element. Once a consumer learns an element API, that API usage remains predictable and reliable throughout the system. Consumer apps/plugins can add opinionated abstractions. This can provide a more opinionated terse API in which consumers can always โ€œescapeโ€ or access the elements of the base library as needed. It's easier to add abstraction layers, it's much more difficult to pull apart the wrong base abstraction. diff --git a/projects/site/src/docs/api-design/index.md b/projects/site/src/docs/api-design/index.md index d58ca35c3..1a6725050 100644 --- a/projects/site/src/docs/api-design/index.md +++ b/projects/site/src/docs/api-design/index.md @@ -23,9 +23,9 @@ This document is not intended to define the best practices and API design of hig Don't: a practice to avoid Tip: helpful details on rationale for a given guideline details on the risks of not following a guideline - ๐Ÿ Performance: detail about how a guideline impacts performance - ๐ŸŽ“ Learn: resource to learn more about a guideline topic - ๐Ÿšง WIP: details on any work in progress guidance + Performance: detail about how a guideline impacts performance + Learn: resource to learn more about a guideline topic + WIP: details on any work in progress guidance
## Terminology @@ -70,7 +70,7 @@ Consistent element APIs provide consistent developer experience. The recommendat ```html -

hello there!

+

hello there!

diff --git a/projects/site/src/docs/foundations/layout/grid.md b/projects/site/src/docs/foundations/layout/grid.md index c8de1ecd4..0a9f80bed 100644 --- a/projects/site/src/docs/foundations/layout/grid.md +++ b/projects/site/src/docs/foundations/layout/grid.md @@ -254,7 +254,7 @@ You can mix both approaches for the most flexibility. Items with explicit `span`
-

Dashboard

+

Dashboard

@@ -274,28 +274,34 @@ You can mix both approaches for the most flexibility. Items with explicit `span` ```html
- + + - + + - + + - + + - + + - + + diff --git a/projects/site/src/docs/foundations/layout/horizontal.md b/projects/site/src/docs/foundations/layout/horizontal.md index 3369ac0ea..154778bd7 100644 --- a/projects/site/src/docs/foundations/layout/horizontal.md +++ b/projects/site/src/docs/foundations/layout/horizontal.md @@ -66,7 +66,7 @@ Set `nve-layout="row"` on a container element to create a horizontal layout: ```html -
-
- -

Menu items

+
+ NV +

Menu items

``` @@ -126,7 +126,7 @@ Horizontal layouts support alignment along both axes: ## Horizontal Layout Examples ```html -
+
``` ### Align Left diff --git a/projects/site/src/docs/foundations/layout/index.md b/projects/site/src/docs/foundations/layout/index.md index f2573e96c..8dea81361 100644 --- a/projects/site/src/docs/foundations/layout/index.md +++ b/projects/site/src/docs/foundations/layout/index.md @@ -76,8 +76,8 @@ Each layout mode supports spacing, alignment, and responsive behavior through a
-

Welcome

-

Create beautiful layouts with ease

+

Welcome

+

Create beautiful layouts with ease

Get Started
@@ -94,7 +94,7 @@ Each layout mode supports spacing, alignment, and responsive behavior through a
- Elements components use Web Components with Shadow DOM encapsulation. Many components manage their own internal layout, for example: nve-card components have built-in layout for nve-card-header, nve-card-content, and nve-card-footer. Applying nve-layout directly to these components may not work as expected due to Shadow DOM boundaries. + Elements components use Web Components with Shadow DOM encapsulation. Many components manage their own internal layout, for example: nve-card components have built-in layout for nve-card-header, nve-card-content, and nve-card-footer. Applying nve-layout directly to these components may not work as expected due to Shadow DOM boundaries.
@@ -103,6 +103,8 @@ Apply the `nve-layout` attribute to **native HTML elements** rather than Element For more details, see the documentation on the [internal-host pattern](/docs/api-design/styles/#removed-host) and [slots](/docs/api-design/slots/) which the library uses in development, as well as [MDN docs](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) on the Shadow DOM. + + ```html
@@ -116,6 +118,8 @@ For more details, see the documentation on the [internal-host pattern](/docs/api ``` + + ## When to Use Each Layout Type ### Use Horizontal Layout (`row`) when: @@ -214,7 +218,7 @@ Or use the short hand to just pad the x and y axes. - `pad-y:md` ```html -
+
``` ### Padding Top @@ -299,11 +303,11 @@ You can compose layout attributes to create sophisticated designs: ```html
- + NV

Build Faster

Create stunning layouts without writing CSS

- Start Building + Start Building Learn More
@@ -311,19 +315,19 @@ You can compose layout attributes to create sophisticated designs:
-

Dashboard

+

Dashboard

Settings
diff --git a/projects/site/src/docs/foundations/layout/responsive/container.md b/projects/site/src/docs/foundations/layout/responsive/container.md index a8adb4b9a..c19d2a8a0 100644 --- a/projects/site/src/docs/foundations/layout/responsive/container.md +++ b/projects/site/src/docs/foundations/layout/responsive/container.md @@ -43,7 +43,7 @@ Conditional gap sizing example: `nve-layout="row &sm|gap:xxs &md|gap:md &lg|gap: ```html
-
+
@@ -56,7 +56,7 @@ Conditional gap sizing example: `nve-layout="row &sm|gap:xxs &md|gap:md &lg|gap:
- The extra div wrapper explicitly defines the container element for queries. This design keeps the utility minimalโ€”elements with & syntax automatically use their parent as the container without requiring manual container specification. + The extra div wrapper explicitly defines the container element for queries. This design keeps the utility minimalโ€”elements with & syntax automatically use their parent as the container without requiring manual container specification.
@@ -141,10 +141,10 @@ Or: ```html
- - - - +
+
+
+
``` @@ -163,7 +163,7 @@ Since hiding elements only affects the display of the element itself and not the
- Element visibility (hiding) uses the separate nve-display attribute rather than nve-layout. This distinction exists because visibility control only affects the element itself, while layout properties affect how the parent arranges children. + Element visibility (hiding) uses the separate nve-display attribute rather than nve-layout. This distinction exists because visibility control only affects the element itself, while layout properties affect how the parent arranges children.
@@ -203,7 +203,7 @@ Example combining both: ```html
-
+
Always visible
Hidden when container โ‰ฅ 320px
diff --git a/projects/site/src/docs/foundations/layout/responsive/index.md b/projects/site/src/docs/foundations/layout/responsive/index.md index 3e890544a..c9e355a5b 100644 --- a/projects/site/src/docs/foundations/layout/responsive/index.md +++ b/projects/site/src/docs/foundations/layout/responsive/index.md @@ -61,7 +61,7 @@ Container queries respond to the **width of the parent container**, making them ```html
-
+
@@ -98,7 +98,7 @@ Viewport queries respond to the **browser window width**, providing traditional ```html -
+
@@ -164,8 +164,8 @@ You can use both systems together for the most flexibility:

Responsive Card Example

- -

This card demonstrates combining container and viewport queries.

+ NV +

This card demonstrates combining container and viewport queries.

@@ -174,8 +174,8 @@ You can use both systems together for the most flexibility:

Responsive Card Example

- -

This card demonstrates combining container and viewport queries.

+ NV +

This card demonstrates combining container and viewport queries.

diff --git a/projects/site/src/docs/foundations/layout/responsive/viewport.md b/projects/site/src/docs/foundations/layout/responsive/viewport.md index c351dffcf..2fe5e7c29 100644 --- a/projects/site/src/docs/foundations/layout/responsive/viewport.md +++ b/projects/site/src/docs/foundations/layout/responsive/viewport.md @@ -41,7 +41,7 @@ The at-symbol-based `@breakpoint-size|...` API adds the breakpoint size before t Conditional gap sizing example: `nve-layout="row @sm|gap:xs @md|gap:md @lg|gap:xxl"`. The size value after the `:` corresponds to one of the nine [spacing](/docs/foundations/layout/#layout-gap-spacing)/[padding](/docs/foundations/layout/#layout-padding) system values. ```html -
+
@@ -134,10 +134,10 @@ Or: ```html
- - - - +
+
+
+
``` @@ -156,7 +156,7 @@ Since hiding elements only affects the display of the element itself and not the
- Element visibility (hiding) uses the separate nve-display attribute rather than nve-layout. This distinction exists because visibility control only affects the element itself, while layout properties affect how the parent arranges children. + Element visibility (hiding) uses the separate nve-display attribute rather than nve-layout. This distinction exists because visibility control only affects the element itself, while layout properties affect how the parent arranges children.
@@ -193,7 +193,7 @@ The viewport query responsive system allows elements to adapt based on the brows Example combining both: ```html -
+
Always visible
Hidden when viewport โ‰ฅ 768px
diff --git a/projects/site/src/docs/foundations/layout/vertical.md b/projects/site/src/docs/foundations/layout/vertical.md index 09d0236d9..5b37b0e4d 100644 --- a/projects/site/src/docs/foundations/layout/vertical.md +++ b/projects/site/src/docs/foundations/layout/vertical.md @@ -66,26 +66,28 @@ Set `nve-layout="column"` on a container element to create a vertical layout: ```html -