NV Elements Catalog Starters Repo System Themes About Getting Started Changelog Metrics Support Accessibility Contributions Requests Migration Deprecations Integrations Installation MCP CLI Lint Angular Bundles Extensions Go Hugo Import Maps Lit NextJS Nuxt Preact React SolidJS Svelte TypeScript Vue Foundations Typography Iconography Themes Design Tokens Size & Space Objects Interactions Support Status Color Animation Fonts Layers Custom Layout Horizontal Vertical Grid Popovers i18n Visualization View Transitions Elements Accordion Alert Avatar Badge Breadcrumb Button Button Group Card Chat Message Checkbox Color Combobox Copy Button Datagrid Integrations Column Action Column Alignment Column Fixed Column width Container Card Display Settings Footer Heatmap Keynav Multi Select Pagination Panel Detail Panel Grid Performance Placeholder Row Action Row Groups Row Sort Scroll Height Single Select Stripe Date Datetime Dialog Divider Dot Drawer Dropdown Dropdown Group Dropzone File Forms Validation Actions Control Icon Icon Button Input Input Group Logo Menu Month Notification Page Page Header Page Loader Pagination Panel Progressive Filter Chip Progress Bar Progress Ring Password Preferences Input Pulse Radio Range Resize Handle Search Select Skeleton Sort Button Sparkline Star Rating Steps Switch Tabs Tag Textarea Time Toast Toggletip Toolbar Tooltip Tree Week Patterns Authentication Browse Dashboard Editor Empty States Heatmap Keyboard Shortcut Logging Media Onboarding Panel Responsive Search Subheader Trend Code Codeblock Monaco Input Diff Input Editor Diff Editor Problems Markdown Markdown CSS Utility Labs Responsive Layout Viewport Container Patterns Forms API Design Properties & Attributes Slots Registration CustomEvents Stateless Composition Styles Packaging Glossary Logs Internal Guidelines Agent Harness Documentation Examples TypeScript Testing Unit Testing Accessibility Testing Lighthouse Testing SSR Testing Visual Testing Troubleshooting Component Creation Internal Examples All Examples

Stateless

Escape Hatch - anti-pattern

Elements are intentionally as stateless as possible. This means elements do not maintain any internal state but rather only render based on the information/state passed to the element via properties/attributes. Consider the following example with a modal element.


    

With the modal, the goal is to show or hide based on some application logic. No open or show property exists. The application fully controls the logic to determine whether to show the modal.

Warning: you can hide Elements via CSS, hidden attribute or add/remove them from the DOM. Elements should remain visible by default, providing the most flexibility to the consumer.

Instead of defining an open or show state, the element provides a close event. This event fires when the user has clicked the close button within the modal. The modal itself does nothing but emit the event. The modal only notifies the application the user had clicked the close button. The application then determines what it should do.

This is important as it provides a much more flexible API and prevents "escape hatch" APIs. If the modal controls the visibility state, and the application needs to check logic before closing, yet another API would need to intercept before the close event (example: onBeforeClose). Consider how an Angular app would use a stateless example.


    

    

Because the application manages the state, the developer now has the flexibility to intercept and run extra checks before closing the modal. While this is a subtle difference when compared to the stateful version, it provides a more flexible element and keeps the public API surface small.

Tip: a guiding principle for element API design is to ask, “Can you prototype/demo any visual state of the element with using only HTML?”

Synchronizing State

Stateless elements also prevent the state from becoming out of sync. If the modal retains an internal visibility state, this opens up the potential for bugs where the element is out of sync with the application state. View the demos below to learn more detail about the risks of stateful leaf elements.

🎓 Learn: stateless vs stateful demo in Angular 🎓 Learn: stateless vs stateful demo in React

Stateful Events - anti-pattern

Do not emit/reflect events in response to setting state on the element. This is unnecessary from the consumer API as the application component does not need event updates for the state it maintains. Example: when you set an expand property, do not emit an expand-change event in response. Only user interactions, not property updates, should trigger events.

Reflective state update events can also trigger infinite loops in certain situations.

Always derive internal state from incoming properties and not user actions. Example, setting a expanded property on an element can cause the element to set css classes and aria-* attributes to visually and semantically represent the expanded state.

🎓 Learn: web.dev Custom Element Event Best Practices

🎓 Case Study: a tag element setting child badge component to align visual status