docs/shadow-parts-guidelines.md
<sub><b>TABLE OF CONTENTS</b></sub>
CSS Shadow Parts: The CSS shadow parts module defines the ::part() pseudo-element that can be set on a shadow host. Using this pseudo-element, you can enable shadow hosts to expose the selected element in the shadow tree to the outside page for styling purposes. 1
Ionic Framework components that use Shadow DOM expose CSS Shadow Parts to enable custom styling by end users.
This document establishes a standardized naming convention for CSS Shadow Parts in Ionic Framework components.
native, wrapper, inner, container, and content wherever they apply before inventing new names.detail-icon, supporting-text).backdrop, handle, label).Name parts by what the element does, not where it appears. Ask what role the element plays:
| Name | Role |
|---|---|
native | Is it a native HTML element that the user interacts with (e.g. <button>, <a>, <input>, <textarea>)? |
wrapper | Is it a native <label> element that wraps the whole form control? |
inner | Is it the inner layout wrapper around the main content? It may wrap only the default slot (e.g. ion-list-header), or a container plus slot(s) (e.g. ion-item, ion-item-divider, ion-item-option) when present. |
container | Does it wrap the main content itself (default slot or native control)? |
content | Is it the main user-content area of an overlay or primary content region? |
The following examples show the correct usage for the standard parts.
nativeWhat it does: The element the user directly interacts with - the native button, anchor, or form control (e.g. <button>, <a>, <textarea>, <input>).
ion-item, ion-button, ion-textarea.ion-item - the interactive element is the <button>, <a>, or <div> (TagType):
const TagType = clickable ? (href === undefined ? 'button' : 'a') : ('div' as any);
return (
<Host>
<TagType class="item-native" part="native">
<slot name="start"></slot>
<div class="item-inner" part="inner">
<div class="input-wrapper" part="container">
<slot></slot>
</div>
<slot name="end"></slot>
</div>
</TagType>
</Host>
);
ion-textarea - the interactive element is the native <textarea>:
<div class="native-wrapper" part="container">
<textarea class="native-textarea" part="native">
{value}
</textarea>
</div>
wrapperWhat it does: The HTML <label> element that wraps the entire form control. Clicking anywhere on it focuses the control.
<label> that wraps the form control.ion-select, ion-textarea, ion-input, ion-checkbox, ion-toggle, ion-radio, ion-range.ion-select - the <label> has part="wrapper":
<label class="select-wrapper" part="wrapper">
{this.renderLabelContainer()}
<div class="select-wrapper-inner" part="inner">
<slot name="start"></slot>
<div class="native-wrapper" part="container">...</div>
<slot name="end"></slot>
</div>
</label>
ion-textarea - the <label> has part="wrapper":
<label class="textarea-wrapper" part="wrapper">
{this.renderLabelContainer()}
<div class="textarea-wrapper-inner" part="inner">
<slot name="start"></slot>
<div class="native-wrapper" part="container">...</div>
<slot name="end"></slot>
</div>
</label>
innerWhat it does: The inner layout wrapper around the main content. It may wrap only the default slot (e.g. ion-list-header), or it may wrap a container and the slot(s) (e.g. start, end) that sit alongside the main content. In ion-item, and ion-item-divider, the start slot is a sibling of this element. In ion-select, both start and end slots are inside this element.
start/end slots).ion-list-header (.list-header-inner wraps only the default slot), ion-item (.item-inner), ion-item-divider (.item-divider-inner), ion-select (.select-wrapper-inner).ion-list-header - .list-header-inner wraps only the default slot (no container, no start/end slots):
<div class="list-header-inner" part="inner">
<slot></slot>
</div>
ion-item - .item-inner wraps the container and end slot (start slot is a sibling):
<slot name="start"></slot>
<div class="item-inner" part="inner">
<div class="input-wrapper" part="container">
<slot></slot>
</div>
<slot name="end"></slot>
</div>
ion-item-divider - .item-divider-inner wraps the container and end slot (start slot is a sibling):
<slot name="start"></slot>
<div class="item-divider-inner" part="inner">
<div class="item-divider-wrapper" part="container">
<slot></slot>
</div>
<slot name="end"></slot>
</div>
ion-select - .select-wrapper-inner arranges start slot, container, end slot:
<div class="select-wrapper-inner" part="inner">
<slot name="start"></slot>
<div class="native-wrapper" part="container"></div>
<slot name="end"></slot>
</div>
containerWhat it does: Wraps the main content - either the default slot (for item-like components) or the native control and its immediate content (for form controls like select, textarea).
content instead).ion-item (.input-wrapper around default slot), ion-item-divider (.item-divider-wrapper), ion-select (.native-wrapper around select text + listbox), ion-textarea (.native-wrapper around <textarea>).From the examples above:
ion-item - .input-wrapper wraps the default <slot>:
<slot name="start"></slot>
<div class="item-inner" part="inner">
<div class="input-wrapper" part="container">
<slot></slot>
</div>
<slot name="end"></slot>
</div>
ion-select - .native-wrapper wraps the select text and listbox:
<div class="select-wrapper-inner" part="inner">
<slot name="start"></slot>
<div class="native-wrapper" part="container">
{this.renderSelectText()}
{this.renderListbox()}
</div>
<slot name="end"></slot>
</div>
ion-textarea - .native-wrapper wraps the <textarea>:
<label class="textarea-wrapper" part="wrapper">
{this.renderLabelContainer()}
<div class="textarea-wrapper-inner" part="inner">
<slot name="start"></slot>
<div class="native-wrapper" part="container">
<textarea class="native-textarea" part="native">
{value}
</textarea>
</div>
<slot name="end"></slot>
</div>
</label>
contentWhat it does: The main user-content area of an overlay or the primary content region (e.g. modal body, toolbar’s main slot).
ion-modal, ion-popover, ion-accordion, ion-toolbar (the div that wraps the default slot inside the toolbar container).ion-modal - content wraps the default <slot> which is the primary content:
<div class="modal-content" part="content">
<slot></slot>
</div>
ion-toolbar - content wraps the default <slot> which is the primary content:
<div class="toolbar-container" part="container">
<slot name="start"></slot>
<slot name="secondary"></slot>
<div class="toolbar-content" part="content">
<slot></slot>
</div>
<slot name="primary"></slot>
<slot name="end"></slot>
</div>
Components may also expose specialized parts for specific elements. The following parts are reused across multiple components:
| Name | Description |
|---|---|
background | Background elements (e.g., ion-content, ion-toolbar) |
backdrop | Backdrop elements. Must only be used on <ion-backdrop> components. (e.g., ion-modal, ion-popover, ion-menu) |
label | Label text elements - not the HTML <label> (see standard part wrapper) |
supporting-text | Supporting text elements |
helper-text | Helper text elements |
error-text | Error text elements |
icon | Icon elements. Must only be used on <ion-icon> components. Use specific names like detail-icon, close-icon when the icon serves a distinct purpose (e.g., ion-item uses detail-icon, ion-fab-button uses close-icon) |
handle | Handle elements (e.g., ion-modal, ion-toggle) |
track | Track elements (e.g., ion-toggle, ion-progress-bar) |
mark | Checkmark or indicator marks (e.g., ion-checkbox, ion-radio) |
When to create new specialized parts:
native, wrapper, inner, container, content) when they applyheader, text, arrow, scroll for component-specific elements)Shadow parts must be documented in the component's JSDoc comments using the @part tag. The following example demonstrates the proper documentation format:
/**
* @part native - The native HTML button, anchor or div element that wraps all child elements.
* @part inner - The inner wrapper element that arranges the item content.
* @part container - The wrapper element that contains the default slot.
* @part detail-icon - The chevron icon for the item. Only applies when `detail="true"`.
*/
MDN Documentation - CSS shadow parts, https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Shadow_parts ↩