Skip to content

Commit 1d7b286

Browse files
docs(core): add guidelines for naming CSS shadow parts (#30931)
Adds guidelines document establishing standardized naming conventions for CSS Shadow Parts in Ionic Framework components. --------- Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
1 parent 6ea186d commit 1d7b286

4 files changed

Lines changed: 289 additions & 0 deletions

File tree

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ It is based on <a href="https://www.webcomponents.org/introduction">Web Componen
2323
| [Contributing](./CONTRIBUTING.md) | How to contribute including creating pull requests, commit message guidelines, and more. |
2424
| [Component Guide](./component-guide.md) | Guidelines for implementing component states, accessibility, and more. |
2525
| [Sass Guidelines](./sass-guidelines.md) | Outlines scenarios where Sass members and comments should be used. |
26+
| [CSS Shadow Parts Guidelines](./shadow-parts-guidelines.md) | Guidelines for CSS shadow parts in components. |
2627

2728
## Packages
2829

docs/component-guide.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* [Example Components](#example-components-4)
1818
* [Component Structure](#component-structure-1)
1919
- [Converting Scoped to Shadow](#converting-scoped-to-shadow)
20+
- [Sass Variables](#sass-variables)
21+
- [CSS Shadow Parts](#css-shadow-parts)
2022
- [RTL](#rtl)
2123
- [Adding New Components with Native Input Support](#adding-new-components-with-native-input-support)
2224
* [Angular Integration](#angular-integration)
@@ -722,6 +724,14 @@ There will be some CSS issues when converting to shadow. Below are some of the d
722724
:host-context(ion-toolbar:not(.ion-color)):host(:not(.ion-color)) ::slotted(ion-segment-button) {
723725
```
724726
727+
## Sass Variables
728+
729+
For guidelines on when to use Sass Variables, see the [Sass Guidelines](./sass-guidelines.md).
730+
731+
## CSS Shadow Parts
732+
733+
For guidelines on adding CSS shadow parts, see the [CSS Shadow Parts Guidelines](./shadow-parts-guidelines.md).
734+
725735
## RTL
726736
727737
When you need to support both LTR and RTL modes, try to avoid using values such as `left` and `right`. For certain CSS properties, you can use the appropriate mixin to have this handled for you automatically.

docs/sass-guidelines.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Sass Guidelines
22

3+
<sub><b>TABLE OF CONTENTS</b></sub>
34
- [Definitions](#definitions)
45
- [Scope](#scope)
56
- [Historical Usage](#historical-usage)

docs/shadow-parts-guidelines.md

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
# CSS Shadow Parts Guidelines
2+
3+
<sub><b>TABLE OF CONTENTS</b></sub>
4+
- [Definitions](#definitions)
5+
- [Scope](#scope)
6+
- [General Guidelines](#general-guidelines)
7+
- [Standard Parts](#standard-parts)
8+
- [Specialized Parts](#specialized-parts)
9+
- [Documentation](#documentation)
10+
11+
## Definitions
12+
13+
**CSS Shadow Parts:** The CSS shadow parts module defines the [::part()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/::part) pseudo-element that can be set on a [shadow host](https://developer.mozilla.org/en-US/docs/Glossary/Shadow_tree). 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]
14+
15+
## Scope
16+
17+
Ionic Framework components that use Shadow DOM expose CSS Shadow Parts to enable custom styling by end users.
18+
19+
This document establishes a standardized naming convention for CSS Shadow Parts in Ionic Framework components.
20+
21+
## General Guidelines
22+
23+
1. **Attempt to use standard parts first**: Use `native`, `wrapper`, `inner`, `container`, and `content` wherever they apply before inventing new names.
24+
2. **Use semantic, kebab-case names**: Choose descriptive names that communicate the role of the element (for example, `detail-icon`, `supporting-text`).
25+
3. **Reuse names for the same concept**: Use the same part name across components when the element serves the same role (for example, `backdrop`, `handle`, `label`).
26+
27+
## Standard Parts
28+
29+
**Name parts by what the element does, not where it appears.** Ask what role the element plays:
30+
31+
| Name | Role |
32+
| --- | --- |
33+
| `native` | Is it a native HTML element that the user interacts with (e.g. `<button>`, `<a>`, `<input>`, `<textarea>`)? |
34+
| `wrapper` | Is it a native `<label>` element that wraps the whole form control? |
35+
| `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. |
36+
| `container` | Does it wrap the main content itself (default slot or native control)? |
37+
| `content` | Is it the main user-content area of an overlay or primary content region? |
38+
39+
The following examples show the correct usage for the standard parts.
40+
41+
### `native`
42+
43+
**What it does:** The element the user directly interacts with - the native button, anchor, or form control (e.g. `<button>`, `<a>`, `<textarea>`, `<input>`).
44+
45+
- **Use when**: The element receives click/focus/input from the user.
46+
- **Examples**: `ion-item`, `ion-button`, `ion-textarea`.
47+
48+
**ion-item** - the interactive element is the `<button>`, `<a>`, or `<div>` (`TagType`):
49+
50+
```tsx
51+
const TagType = clickable ? (href === undefined ? 'button' : 'a') : ('div' as any);
52+
53+
return (
54+
<Host>
55+
<TagType class="item-native" part="native">
56+
<slot name="start"></slot>
57+
<div class="item-inner" part="inner">
58+
<div class="input-wrapper" part="container">
59+
<slot></slot>
60+
</div>
61+
<slot name="end"></slot>
62+
</div>
63+
</TagType>
64+
</Host>
65+
);
66+
```
67+
68+
**ion-textarea** - the interactive element is the native `<textarea>`:
69+
70+
```tsx
71+
<div class="native-wrapper" part="container">
72+
<textarea class="native-textarea" part="native">
73+
{value}
74+
</textarea>
75+
</div>
76+
```
77+
78+
### `wrapper`
79+
80+
**What it does:** The HTML `<label>` element that wraps the entire form control. Clicking anywhere on it focuses the control.
81+
82+
- **Use when**: The element is the `<label>` that wraps the form control.
83+
- **Examples**: `ion-select`, `ion-textarea`, `ion-input`, `ion-checkbox`, `ion-toggle`, `ion-radio`, `ion-range`.
84+
85+
**ion-select** - the `<label>` has `part="wrapper"`:
86+
87+
```tsx
88+
<label class="select-wrapper" part="wrapper">
89+
{this.renderLabelContainer()}
90+
<div class="select-wrapper-inner" part="inner">
91+
<slot name="start"></slot>
92+
<div class="native-wrapper" part="container">...</div>
93+
<slot name="end"></slot>
94+
</div>
95+
</label>
96+
```
97+
98+
**ion-textarea** - the `<label>` has `part="wrapper"`:
99+
100+
```tsx
101+
<label class="textarea-wrapper" part="wrapper">
102+
{this.renderLabelContainer()}
103+
<div class="textarea-wrapper-inner" part="inner">
104+
<slot name="start"></slot>
105+
<div class="native-wrapper" part="container">...</div>
106+
<slot name="end"></slot>
107+
</div>
108+
</label>
109+
```
110+
111+
### `inner`
112+
113+
**What 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.
114+
115+
- **Use when**: The element is the inner layout wrapper (with or without a separate container and `start`/`end` slots).
116+
- **Examples**: `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`).
117+
118+
**ion-list-header** - `.list-header-inner` wraps only the default slot (no container, no `start`/`end` slots):
119+
120+
```tsx
121+
<div class="list-header-inner" part="inner">
122+
<slot></slot>
123+
</div>
124+
```
125+
126+
**ion-item** - `.item-inner` wraps the container and `end` slot (`start` slot is a sibling):
127+
128+
```tsx
129+
<slot name="start"></slot>
130+
<div class="item-inner" part="inner">
131+
<div class="input-wrapper" part="container">
132+
<slot></slot>
133+
</div>
134+
<slot name="end"></slot>
135+
</div>
136+
```
137+
138+
**ion-item-divider** - `.item-divider-inner` wraps the container and `end` slot (`start` slot is a sibling):
139+
140+
```tsx
141+
<slot name="start"></slot>
142+
<div class="item-divider-inner" part="inner">
143+
<div class="item-divider-wrapper" part="container">
144+
<slot></slot>
145+
</div>
146+
<slot name="end"></slot>
147+
</div>
148+
```
149+
150+
**ion-select** - `.select-wrapper-inner` arranges `start` slot, container, `end` slot:
151+
152+
```tsx
153+
<div class="select-wrapper-inner" part="inner">
154+
<slot name="start"></slot>
155+
<div class="native-wrapper" part="container"></div>
156+
<slot name="end"></slot>
157+
</div>
158+
```
159+
160+
### `container`
161+
162+
**What 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).
163+
164+
- **Use when**: The element wraps the default slot, or wraps the native control (and any immediate content like listbox or slots inside it).
165+
- **Don’t use when**: The element is the main content area of an overlay (use `content` instead).
166+
- **Examples**: `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>`).
167+
168+
From the examples above:
169+
170+
**ion-item** - `.input-wrapper` wraps the default `<slot>`:
171+
172+
```tsx
173+
<slot name="start"></slot>
174+
<div class="item-inner" part="inner">
175+
<div class="input-wrapper" part="container">
176+
<slot></slot>
177+
</div>
178+
<slot name="end"></slot>
179+
</div>
180+
```
181+
182+
**ion-select** - `.native-wrapper` wraps the select text and listbox:
183+
184+
```tsx
185+
<div class="select-wrapper-inner" part="inner">
186+
<slot name="start"></slot>
187+
<div class="native-wrapper" part="container">
188+
{this.renderSelectText()}
189+
{this.renderListbox()}
190+
</div>
191+
<slot name="end"></slot>
192+
</div>
193+
```
194+
195+
**ion-textarea** - `.native-wrapper` wraps the `<textarea>`:
196+
197+
```tsx
198+
<label class="textarea-wrapper" part="wrapper">
199+
{this.renderLabelContainer()}
200+
<div class="textarea-wrapper-inner" part="inner">
201+
<slot name="start"></slot>
202+
<div class="native-wrapper" part="container">
203+
<textarea class="native-textarea" part="native">
204+
{value}
205+
</textarea>
206+
</div>
207+
<slot name="end"></slot>
208+
</div>
209+
</label>
210+
```
211+
212+
### `content`
213+
214+
**What it does:** The main user-content area of an overlay or the primary content region (e.g. modal body, toolbar’s main slot).
215+
216+
- **Use when**: The element is the primary content area where users see the main content (overlay body, or primary slot inside something like a toolbar).
217+
- **Examples**: `ion-modal`, `ion-popover`, `ion-accordion`, `ion-toolbar` (the div that wraps the default slot inside the toolbar container).
218+
219+
**ion-modal** - `content` wraps the default `<slot>` which is the primary content:
220+
221+
```tsx
222+
<div class="modal-content" part="content">
223+
<slot></slot>
224+
</div>
225+
```
226+
227+
**ion-toolbar** - `content` wraps the default `<slot>` which is the primary content:
228+
229+
```tsx
230+
<div class="toolbar-container" part="container">
231+
<slot name="start"></slot>
232+
<slot name="secondary"></slot>
233+
<div class="toolbar-content" part="content">
234+
<slot></slot>
235+
</div>
236+
<slot name="primary"></slot>
237+
<slot name="end"></slot>
238+
</div>
239+
```
240+
241+
## Specialized Parts
242+
243+
Components may also expose specialized parts for specific elements. The following parts are reused across multiple components:
244+
245+
| Name | Description |
246+
| --- | --- |
247+
| `background` | Background elements (e.g., `ion-content`, `ion-toolbar`) |
248+
| `backdrop` | Backdrop elements. **Must only be used on `<ion-backdrop>` components.** (e.g., `ion-modal`, `ion-popover`, `ion-menu`) |
249+
| `label` | Label text elements - not the HTML `<label>` (see standard part `wrapper`) |
250+
| `supporting-text` | Supporting text elements |
251+
| `helper-text` | Helper text elements |
252+
| `error-text` | Error text elements |
253+
| `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`) |
254+
| `handle` | Handle elements (e.g., `ion-modal`, `ion-toggle`) |
255+
| `track` | Track elements (e.g., `ion-toggle`, `ion-progress-bar`) |
256+
| `mark` | Checkmark or indicator marks (e.g., `ion-checkbox`, `ion-radio`) |
257+
258+
**When to create new specialized parts:**
259+
- Use standard parts (`native`, `wrapper`, `inner`, `container`, `content`) when they apply
260+
- Reuse existing specialized parts (listed above) when they match the element's role
261+
- Create component-specific specialized parts for elements that don't fit standard patterns or existing specialized parts
262+
- Use descriptive, semantic names (e.g., `header`, `text`, `arrow`, `scroll` for component-specific elements)
263+
264+
## Documentation
265+
266+
Shadow parts must be documented in the component's JSDoc comments using the `@part` tag. The following example demonstrates the proper documentation format:
267+
268+
```tsx
269+
/**
270+
* @part native - The native HTML button, anchor or div element that wraps all child elements.
271+
* @part inner - The inner wrapper element that arranges the item content.
272+
* @part container - The wrapper element that contains the default slot.
273+
* @part detail-icon - The chevron icon for the item. Only applies when `detail="true"`.
274+
*/
275+
```
276+
277+
[^1]: MDN Documentation - CSS shadow parts, https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Shadow_parts

0 commit comments

Comments
 (0)