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? |
control | Is it the group of the label and the native control, laid out together inside a form control? |
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-back-button, ion-segment-button.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>
);
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-select - the <label> has part="wrapper":
<label class="select-wrapper" part="wrapper">
<div class="select-start" part="start">
<slot name="start"></slot>
</div>
<div class="select-control" part="control">
{this.renderLabel()}
<div class="native-wrapper" part="container">...</div>
</div>
<div class="select-end" part="end">
<slot name="end"></slot>
</div>
</label>
The start and end slots are wrapped in their own containers rather than sitting directly
inside the label, so they can be styled and measured independently of the control.
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.
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-item-option (.item-option-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>
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).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-control" part="control">
{this.renderLabel()}
<div class="native-wrapper" part="container">
{this.renderSelectText()}
{this.renderListbox()}
</div>
</div>
controlWhat it does: Groups the label and the native control so they lay out together, separately from the start and end slot containers on either side of them.
label-placement can switch between a row and a column without moving the slots.start/end slots (use inner instead).ion-select (.select-control).ion-select - .select-control groups the label and the native wrapper, with the slots as siblings:
<div class="select-start" part="start">
<slot name="start"></slot>
</div>
<div class="select-control" part="control">
{this.renderLabel()}
<div class="native-wrapper" part="container">...</div>
</div>
<div class="select-end" part="end">
<slot name="end"></slot>
</div>
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) |
start | The container holding the start slot, when the slot is wrapped so it can be styled or measured on its own (e.g., ion-select) |
end | The container holding the end slot, under the same conditions as start (e.g., ion-select) |
bottom | The area below the control that holds supporting text and counters (e.g., ion-select) |
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 ↩