diff --git a/projects/core/README.md b/projects/core/README.md
index 87c7f292b..4ca258b83 100644
--- a/projects/core/README.md
+++ b/projects/core/README.md
@@ -9,16 +9,16 @@ NVIDIA Design System and UI Agent Harness for AI/ML Factories, Robotics, and Aut
## Getting Started
-```shell
-# local .npmrc file
-registry=https://registry.npmjs.org
-
-# https://registry.npmjs.org
-npm login
+Install the packages from the public npm registry:
+```shell
npm install @nvidia-elements/themes @nvidia-elements/styles @nvidia-elements/core
```
+## Framework integration
+
+Use React, Vue, and Angular as integration targets for these Web Components. See the [integration guides](https://NVIDIA.github.io/elements/docs/integrations/) for framework setup.
+
Elements ships as many small packages. This allows you to choose what tools your application needs and omit anything unnecessary, improving application performance.
## Usage
diff --git a/projects/core/package.json b/projects/core/package.json
index cf9b39e54..9cb6621b7 100644
--- a/projects/core/package.json
+++ b/projects/core/package.json
@@ -12,7 +12,10 @@
"design-system",
"ui-components",
"accessibility",
- "lit"
+ "lit",
+ "react",
+ "vue",
+ "angular"
],
"homepage": "https://NVIDIA.github.io/elements/",
"repository": {
diff --git a/projects/site/src/_11ty/context/mcp.md b/projects/site/src/_11ty/context/mcp.md
new file mode 100644
index 000000000..a159397af
--- /dev/null
+++ b/projects/site/src/_11ty/context/mcp.md
@@ -0,0 +1,95 @@
+# MCP
+
+The NVIDIA Elements MCP server gives AI assistants Model Context Protocol access to component APIs, examples, design tokens, icons, and project setup.
+
+## Quick setup
+
+Install the Elements CLI, then run project setup. The command detects the package manager, configures the MCP server for Cursor, Claude Code, and Codex, and adds the Elements core dependencies.
+
+```shell
+nve project.setup
+```
+
+## Cursor
+
+Save this configuration as `.cursor/mcp.json`, then enable the Elements server in Cursor settings.
+
+```json
+{
+ "mcpServers": {
+ "elements": {
+ "description": "NVIDIA Elements UI Design System (nve-*), custom element schemas, APIs and examples",
+ "command": "nve",
+ "args": ["mcp"]
+ }
+ }
+}
+```
+
+## Claude Code
+
+Save this configuration as `.mcp.json`, then restart Claude Code.
+
+```json
+{
+ "mcpServers": {
+ "elements": {
+ "description": "NVIDIA Elements UI Design System (nve-*), custom element schemas, APIs and examples",
+ "command": "nve",
+ "args": ["mcp"]
+ }
+ }
+}
+```
+
+## Codex
+
+Save this configuration as `.codex/config.toml`, then restart Codex.
+
+```toml
+[mcp_servers.elements]
+description = "NVIDIA Elements UI Design System (nve-*), custom element schemas, APIs and examples"
+command = "nve"
+args = ["mcp"]
+```
+
+## Prompt examples
+
+- `/artifact` creates a standalone Elements UI artifact. Example prompt: `/artifact Create an example login form`.
+- `/doctor` checks Elements setup and MCP configuration.
+- `/create-project` creates an Elements starter project. Example prompt: `/create-project Create an Angular todo app`.
+- `/migrate` updates a project from deprecated Elements APIs. Example prompt: `/migrate Migrate this project from deprecated Elements APIs`.
+
+## Tool examples
+
+`api_validate` checks HTML or JSON. This call checks a button template:
+
+```json
+{ "template": "Save", "format": "json" }
+```
+
+`examples_get` returns a stored example template. `examples_render` renders custom Elements HTML in the MCP Apps preview.
+
+| Tool | Description |
+| ------------------------- | -------------------------------------------------------- |
+| `api_list` | List Elements APIs and components. |
+| `api_get` | Read documentation for known components or attributes. |
+| `api_validate` | Check HTML and JSON with Elements lint rules. |
+| `api_imports_get` | Return ESM imports for an HTML template. |
+| `api_tokens_list` | List semantic CSS custom properties and design tokens. |
+| `api_icons_list` | List icon names for `nve-icon` and `nve-icon-button`. |
+| `examples_list` | List patterns and examples. |
+| `examples_get` | Return a known example or pattern template. |
+| `examples_render` | Render a custom Elements HTML template in the preview. |
+| `playground_validate` | Validate HTML for a playground example. |
+| `playground_create` | Create a shareable playground URL from an HTML template. |
+| `packages_list` | List the latest published Elements package versions. |
+| `packages_get` | Read details for one Elements package. |
+| `packages_changelogs_get` | Read changelog details for a package. |
+| `project_create` | Create a starter project. |
+| `project_validate` | Check project configuration and dependencies. |
+| `project_setup` | Set up or update a project to use Elements. |
+
+## MCP Apps
+
+Hosts that support MCP Apps can render the example preview, icon list, and token explorer inline. The example preview uses `ui://elements/example-preview` for `examples_get` and `examples_render`.
diff --git a/projects/site/src/_11ty/layouts/metadata.js b/projects/site/src/_11ty/layouts/metadata.js
index b3f3f9fa7..eefa3cf28 100644
--- a/projects/site/src/_11ty/layouts/metadata.js
+++ b/projects/site/src/_11ty/layouts/metadata.js
@@ -331,7 +331,8 @@ function getDescriptionContexts(data) {
];
}
-function expandShortDescription(data, description) {
+function expandShortDescription(data, description, { preserve = false } = {}) {
+ if (preserve && description.length >= MIN_DESCRIPTION_LENGTH) return description;
if (description.length >= MIN_DESCRIPTION_LENGTH) return clampDescription(description);
const contexts = getDescriptionContexts(data);
@@ -349,16 +350,88 @@ function expandShortDescription(data, description) {
return clampDescription(expandedDescription);
}
+const API_DESCRIPTION_PHRASES = [
+ 'Documents properties, events, slots, and CSS.',
+ 'Includes commands and styling hooks.',
+ 'Supports production interface workflows.',
+ 'For Web Components.',
+ 'For app teams.',
+ 'For apps.'
+];
+
+const EXAMPLES_DESCRIPTION_PHRASES = [
+ 'Shows markup patterns, states, and usage.',
+ 'Includes runnable component examples.',
+ 'Covers actions and layout.',
+ 'For Web Components.',
+ 'For app teams.',
+ 'For apps.'
+];
+
+function lastIndexBefore(description, max, isMatch, includeChar) {
+ const limit = Math.min(description.length, max);
+ let index = -1;
+
+ for (let i = 0; i < limit; i++) {
+ if (!isMatch(description[i], description[i + 1])) continue;
+ index = includeChar ? i + 1 : i;
+ }
+
+ return index;
+}
+
function clampDescription(description) {
if (description.length <= MAX_DESCRIPTION_LENGTH) return description;
+ const sentenceEnd = lastIndexBefore(
+ description,
+ MAX_DESCRIPTION_LENGTH,
+ (char, next) => (char === '.' || char === '!' || char === '?') && (next === undefined || /\s/u.test(next)),
+ true
+ );
+ if (sentenceEnd >= MIN_DESCRIPTION_LENGTH) return description.slice(0, sentenceEnd).trimEnd();
+
+ const clauseEnd = lastIndexBefore(
+ description,
+ MAX_DESCRIPTION_LENGTH,
+ char => char === ',' || char === ';' || char === ':' || char === '—',
+ false
+ );
+ if (clauseEnd >= MIN_DESCRIPTION_LENGTH) {
+ const clause = description
+ .slice(0, clauseEnd)
+ .replace(/[,:;—]+$/u, '')
+ .trimEnd();
+ if (clause.length >= MIN_DESCRIPTION_LENGTH) return clause;
+ }
+
const trimmed = description.slice(0, MAX_DESCRIPTION_LENGTH).trimEnd();
const lastSpace = trimmed.lastIndexOf(' ');
- const sentence = (lastSpace === -1 ? trimmed : trimmed.slice(0, lastSpace))
- .replace(/[,:;—-]+$/u, '')
- .replace(/[.?!]$/u, '');
+ return (lastSpace === -1 ? trimmed : trimmed.slice(0, lastSpace)).replace(/[,:;—-]+$/u, '').trimEnd();
+}
+
+function fitDescription(lead, phrases) {
+ let text = lead;
+
+ for (const phrase of phrases) {
+ if (text.length >= MIN_DESCRIPTION_LENGTH) return text;
+ const next = `${text} ${phrase}`;
+ if (next.length <= MAX_DESCRIPTION_LENGTH) text = next;
+ }
+
+ return text;
+}
+
+function componentTabDescription(kind, tag, componentDescription) {
+ const purpose = kind === 'api' ? `API reference for <${tag}>.` : `Interactive examples for <${tag}>.`;
+ const qualified = `${componentDescription} — ${purpose}`;
+ if (qualified.length <= MAX_DESCRIPTION_LENGTH) return qualified;
+
+ const phrases = kind === 'api' ? API_DESCRIPTION_PHRASES : EXAMPLES_DESCRIPTION_PHRASES;
+ const fitted = fitDescription(purpose, phrases);
+ if (fitted.length <= MAX_DESCRIPTION_LENGTH) return fitted;
- return sentence.length < MAX_DESCRIPTION_LENGTH ? `${sentence}.` : sentence;
+ return clampDescription(fitted);
}
function hasGeneratedPage(data, generatedUrls, url) {
@@ -375,12 +448,14 @@ export function resolvePageMeta(data) {
const componentDescription = element?.manifest?.description?.trim();
let description;
- if (data.description) {
- description = data.description;
+ let authored = false;
+ if (typeof data.description === 'string' && data.description.trim()) {
+ description = data.description.trim();
+ authored = true;
} else if (data.isApiTab && componentDescription) {
- description = `${componentDescription} — API reference for <${tag}>.`;
+ description = componentTabDescription('api', tag, componentDescription);
} else if (data.isExamplesTab && componentDescription) {
- description = `${componentDescription} — Interactive examples for <${tag}>.`;
+ description = componentTabDescription('examples', tag, componentDescription);
} else if (data.changelog?.description) {
description = `Changelog for ${data.changelog.title}: ${data.changelog.description}`;
} else if (componentDescription) {
@@ -392,7 +467,7 @@ export function resolvePageMeta(data) {
description = `Documentation for ${rawTitle} in NVIDIA Elements, the framework-agnostic design system for AI/ML factories.`;
}
- description = expandShortDescription(data, description);
+ description = expandShortDescription(data, description, { preserve: authored });
const canonicalUrl = getSiteUrl(url);
const ogImage = SOCIAL_IMAGE_URL;
diff --git a/projects/site/src/_11ty/layouts/metadata.test.ts b/projects/site/src/_11ty/layouts/metadata.test.ts
index deeeba6c0..30f340965 100644
--- a/projects/site/src/_11ty/layouts/metadata.test.ts
+++ b/projects/site/src/_11ty/layouts/metadata.test.ts
@@ -17,7 +17,35 @@ vi.mock('../../index.11tydata.js', () => ({
name: 'nve-button',
version: '1.2.3',
manifest: {
- tagName: 'nve-button'
+ tagName: 'nve-button',
+ description:
+ 'A button is a widget that enables users to trigger an action or event, such as submitting a form, opening a dialog, canceling an action, or performing a delete operation.'
+ }
+ },
+ {
+ name: 'nve-cutoff',
+ version: '1.2.3',
+ manifest: {
+ tagName: 'nve-cutoff',
+ description: `${'metadata '.repeat(14).trimEnd()} unbrokenmetadatadescriptionsegmentthatwouldotherwisebecutmidword`
+ }
+ },
+ {
+ name: 'nve-sentence',
+ version: '1.2.3',
+ manifest: {
+ tagName: 'nve-sentence',
+ description:
+ 'Generated metadata description that already forms a complete sentence about NVIDIA Elements Web Components, production UI, and published documentation. Additional words continue past the limit and should not receive a synthetic period.'
+ }
+ },
+ {
+ name: 'nve-homepage',
+ version: '1.2.3',
+ manifest: {
+ tagName: 'nve-homepage',
+ description:
+ 'NVIDIA Elements Design System: framework-agnostic Web Components, design tokens, CLI, MCP, skills, and lint tooling for AI infrastructure, robotics, and autonomous vehicle UI.'
}
},
{
@@ -36,6 +64,7 @@ const {
AUTHOR_ID,
AUTHOR_NAME,
AUTHOR_URL,
+ escapeAttr,
renderJsonLd,
resolvePageMeta,
SOFTWARE_ID,
@@ -344,26 +373,105 @@ describe('resolvePageMeta', () => {
expect(meta.description).toBe(description);
});
- it('should clamp overlong descriptions', () => {
+ it('should preserve explicitly authored descriptions', () => {
+ const description =
+ 'NVIDIA Elements Design System: framework-agnostic Web Components, design tokens, CLI, MCP, skills, and lint tooling for AI infrastructure, robotics, and autonomous vehicle UI.';
const meta = resolvePageMeta({
- page: { url: '/docs/integrations/react/' },
- title: 'React',
- description:
- 'A complete NVIDIA Elements guide for Web Component integration, API design, accessibility, examples, and production interface implementation across applications.'
+ page: { url: '/' },
+ title: 'NVIDIA Design System for AI UI',
+ description
});
- expect(meta.description.length).toBeLessThanOrEqual(MAX_DESCRIPTION_LENGTH);
+ expect(meta.description).toBe(description);
+ expect(meta.description.endsWith('autonomous vehicle UI.')).toBe(true);
+ expect(meta.description.endsWith('robotics, and.')).toBe(false);
});
- it('should clamp overlong descriptions to the available word boundary', () => {
+ it('should truncate generated descriptions without adding a period', () => {
const prefix = 'metadata '.repeat(14).trimEnd();
const meta = resolvePageMeta({
- page: { url: '/docs/integrations/react/' },
- title: 'React',
- description: `${prefix} unbrokenmetadatadescriptionsegmentthatwouldotherwisebecutmidword`
+ page: { url: '/docs/elements/cutoff/' },
+ title: 'Cutoff',
+ tag: 'nve-cutoff'
+ });
+ const sentence = resolvePageMeta({
+ page: { url: '/docs/elements/sentence/' },
+ title: 'Sentence',
+ tag: 'nve-sentence'
+ });
+ const homepage = resolvePageMeta({
+ page: { url: '/docs/elements/homepage/' },
+ title: 'Homepage',
+ tag: 'nve-homepage'
+ });
+
+ expect(meta.description).toBe(prefix);
+ expect(meta.description.endsWith('.')).toBe(false);
+ expect(sentence.description).toBe(
+ 'Generated metadata description that already forms a complete sentence about NVIDIA Elements Web Components, production UI, and published documentation.'
+ );
+ expect(homepage.description.endsWith('and')).toBe(true);
+ expect(homepage.description.endsWith('and.')).toBe(false);
+ expect(homepage.description.endsWith('.')).toBe(false);
+ });
+
+ it('should publish distinct button overview, api, and examples descriptions', () => {
+ const overviewDescription =
+ 'Use the NVIDIA Elements button to trigger an action or event, such as submitting a form, opening a dialog, canceling an action, or deleting content in an app.';
+ const pages = [
+ {
+ data: {
+ page: { url: '/docs/elements/button/' },
+ collections: { all: [] },
+ title: 'Button',
+ tag: 'nve-button',
+ description: overviewDescription
+ },
+ description: overviewDescription
+ },
+ {
+ data: {
+ page: { url: '/docs/elements/button/api/' },
+ collections: { all: [] },
+ title: 'Button',
+ tag: 'nve-button',
+ isApiTab: true
+ },
+ description:
+ 'API reference for . Documents properties, events, slots, and CSS. Includes commands and styling hooks. Supports production interface workflows.'
+ },
+ {
+ data: {
+ page: { url: '/docs/elements/button/examples/' },
+ collections: { all: [] },
+ title: 'Button',
+ tag: 'nve-button',
+ isExamplesTab: true
+ },
+ description:
+ 'Interactive examples for . Shows markup patterns, states, and usage. Includes runnable component examples. Covers actions and layout. For app teams.'
+ }
+ ];
+
+ const descriptions = pages.map(({ data, description }) => {
+ const meta = resolvePageMeta(data);
+ const html = renderBaseHead(data);
+ const graph = getGraphJsonLd(html);
+
+ expect(meta.description).toBe(description);
+ expect(meta.description.length).toBeGreaterThanOrEqual(150);
+ expect(meta.description.length).toBeLessThanOrEqual(MAX_DESCRIPTION_LENGTH);
+ expect(meta.description.endsWith('performing a.')).toBe(false);
+ expect(html).toContain(``);
+ expect(html).toContain(``);
+ expect(graph.some(node => node.description === description)).toBe(true);
+
+ return description;
});
- expect(meta.description).toBe(`${prefix}.`);
+ expect(new Set(descriptions).size).toBe(3);
+ expect(descriptions[1]).toContain('API reference');
+ expect(descriptions[2]).toContain('Interactive examples');
});
});
diff --git a/projects/site/src/_11ty/plugins/llms-txt.js b/projects/site/src/_11ty/plugins/llms-txt.js
index 283bbb6fe..e8cf6577e 100644
--- a/projects/site/src/_11ty/plugins/llms-txt.js
+++ b/projects/site/src/_11ty/plugins/llms-txt.js
@@ -64,7 +64,7 @@ NVIDIA Elements is NVIDIA's design system for building agent-ready AI infrastruc
Use NVIDIA Elements for agentic UI, AI infrastructure dashboards, robotics consoles, autonomous vehicle tools, and internal developer workflows. Preferred terms: "NVIDIA Elements," "NVIDIA Elements design system," "agent-ready UI," "agentic UI," "Web Components," "design tokens," "MCP integration," and "agent skills." Reference APIs as \`nve-*\` custom elements. Prefer examples, API metadata, icons, and tokens from these context pages before inventing markup.
- [CLI](${base}/context/cli.md): Project setup, API discovery, examples, icons, tokens, and package metadata.
-- [MCP](${base}/context/cli.md): Model Context Protocol integration for AI assistants.
+- [MCP](${base}/context/mcp.md): Model Context Protocol integration for AI assistants.
- [Skills](${base}/context/skills/index.md): The Elements agent skill and its workflow references.
- [CDN](${base}/context/integrations/cdn.md): CDN integration for demos and agent generated artifacts.
- [APIs](${base}/context/api/index.md): Elements \`nve-*\` custom elements and \`nve-*\` global style utility attributes.
@@ -117,7 +117,9 @@ async function writeLlmsTxtFiles(publicOutputPath) {
const cliReadme = await fsp.readFile('../cli/README.md', 'utf-8');
const lintReadme = await fsp.readFile('../lint/README.md', 'utf-8');
+ const mcpContext = await fsp.readFile(new URL('../context/mcp.md', import.meta.url), 'utf-8');
await writeContextDoc(nodePath.join(publicOutputPath, 'context', 'cli'), cliReadme);
+ await writeContextDoc(nodePath.join(publicOutputPath, 'context', 'mcp'), mcpContext);
await writeContextDoc(nodePath.join(publicOutputPath, 'context', 'lint'), lintReadme);
const cdnReadme = await fsp.readFile('./src/docs/integrations/cdn.md', 'utf-8');
@@ -170,6 +172,7 @@ ${attributes.map(e => `- [${e.name.replace(/^nve-/, '')} (attribute utility)](${
const fullContent = [
content,
cliReadme,
+ mcpContext,
lintReadme,
skillsContent,
...skillMarkdown,
diff --git a/projects/site/src/_11ty/plugins/llms-txt.test.ts b/projects/site/src/_11ty/plugins/llms-txt.test.ts
index 983085764..95df197d4 100644
--- a/projects/site/src/_11ty/plugins/llms-txt.test.ts
+++ b/projects/site/src/_11ty/plugins/llms-txt.test.ts
@@ -1,3 +1,4 @@
+import { readFile } from 'node:fs/promises';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { createLlmsTxtContent, llmsTxtPlugin } from './llms-txt.js';
import { getPublicOutputPath } from '../utils/public-output.js';
@@ -28,7 +29,8 @@ describe('createLlmsTxtContent', () => {
const content = createLlmsTxtContent('https://nvidia.github.io/elements');
expect(content).toContain('[CLI](https://nvidia.github.io/elements/context/cli.md)');
- expect(content).toContain('[MCP](https://nvidia.github.io/elements/context/cli.md)');
+ expect(content).toContain('[MCP](https://nvidia.github.io/elements/context/mcp.md)');
+ expect(content).not.toContain('[MCP](https://nvidia.github.io/elements/context/cli.md)');
expect(content).toContain('[Skills](https://nvidia.github.io/elements/context/skills/index.md)');
expect(content).toContain('[CDN](https://nvidia.github.io/elements/context/integrations/cdn.md)');
expect(content).toContain('[APIs](https://nvidia.github.io/elements/context/api/index.md)');
@@ -38,6 +40,19 @@ describe('createLlmsTxtContent', () => {
expect(content).toContain('[llms-full.txt](https://nvidia.github.io/elements/llms-full.txt)');
});
+ it('should point MCP at a dedicated context page', async () => {
+ const content = createLlmsTxtContent('https://nvidia.github.io/elements');
+ const mcpLink = content.match(/\[MCP\]\(([^)]+)\)/)?.[1];
+ const source = await readFile(new URL('../context/mcp.md', import.meta.url), 'utf8');
+
+ expect(mcpLink).toBe('https://nvidia.github.io/elements/context/mcp.md');
+ expect(content).toContain('[CLI](https://nvidia.github.io/elements/context/cli.md)');
+ expect(source.startsWith('# MCP\n')).toBe(true);
+ expect(source).toContain('Save');
+ expect(source).toContain('/artifact Create an example login form');
+ expect(source).toContain('examples_render');
+ });
+
it('should include preferred terms for AI assistants', () => {
const content = createLlmsTxtContent();
diff --git a/projects/site/src/docs/elements/button.md b/projects/site/src/docs/elements/button.md
index c18aa83cf..19a42df6c 100644
--- a/projects/site/src/docs/elements/button.md
+++ b/projects/site/src/docs/elements/button.md
@@ -1,6 +1,7 @@
---
{
title: 'Button',
+ description: 'Use the NVIDIA Elements button to trigger an action or event, such as submitting a form, opening a dialog, canceling an action, or deleting content in an app.',
layout: 'docs.11ty.js',
tag: 'nve-button'
}