← All articles

Understanding the slot Attribute Using Ionic's IonItem as a Reference

How IonItem's start/end/default slots work in Shadow DOM, ::slotted() styling, and flex layout in item-inner.

Published
Understanding the slot Attribute Using Ionic's IonItem as a Reference cover image

Ionic Framework components differ in attributes, but item components usually have a slot attribute. Let's look at what that is and how it is implemented.

slot as Web Components

Ionic components have many custom attributes like color and mode, but the slot attribute is the standard spec for inserting DOM into the slot element defined in Web Components Shadow DOM.

https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/slot

For Ionic Framework's IonItem, the template looks like this (only the slot-related parts are shown; the rest is omitted):

<ion-item>
  <slot name="start"></slot>
  <div class="item-inner">
    <div class="input-wrapper">
      <slot></slot>
    </div>
    <slot name="end"></slot>
  </div>
</ion-item>

Everything under ion-item is Shadow DOM. You use the template like this:

<ion-item>
  <ion-icon name="close" slot="start"></ion-icon>
  <ion-label>
    <h2>H2 Title Text</h2>
    <p>Button on right</p>
  </ion-label>
  <ion-button fill="outline" slot="end">View</ion-button>
</ion-item>

Then the HTML above is inserted where matching slot[name] slots are. In other words:

  • ion-thumbnail[slot=start] goes in ion-item > slot[name=start]
  • ion-button[slot=end] goes in ion-item > .item-inner > slot[name=end]
  • ion-label without a slot goes in ion-item > .item-inner > .input-wrapper > slot

Ionic Framework is built on web standards, so this follows the Web Components spec as-is.

Styling slot

Let's briefly look at how styling works. In CSS, slotted content is targeted with ::slotted(). Part of the styling above looks like this:

<ion-item>
  <slot name="start"></slot>
  <div class="item-inner">
    <div class="input-wrapper">
      <slot></slot>
    </div>
    <slot name="end"></slot>
  </div>
</ion-item>

::slotted(ion-icon) {
  font-size: 1.6em;
}

::slotted(ion-button) {
  --margin-top: 0;
  --margin-bottom: 0;
  --margin-start: 0;
  --margin-end: 0;

  z-index: 1;
}

::slotted(ion-label) {
  flex: 1;
}

Slotted ion-icon gets font-size: 1.6em. For ion-button, default margins are zeroed out, and ion-label gets flex: 1 because of the parent .input-wrapperβ€”for when there are multiple ion-label elements.

The heart of this design is .item-inner and .input-wrapper (partially omitted):

.item-inner {
  display: flex;

  // This is required to work with an inset highlight
  position: relative;

  flex: 1;
  flex-direction: inherit;
  align-items: inherit;
  align-self: stretch;
}

.input-wrapper {
  display: flex;

  flex: 1;
  flex-direction: inherit;

  align-items: inherit;
  align-self: stretch;

  text-overflow: ellipsis;

  overflow: inherit;
  box-sizing: border-box;
}

Elements with [slot=start] are usually width-capped items (ion-icon, ion-button, and so on), so the unnamed slot needs to fill the remaining space. That is why .item-inner has align-self: stretch. The same applies to input-wrapper under .item-inner.

This yields a structure where "[slot=start] takes space first, then [slot=end] inside .item-inner, and the rest goes to the unnamed slot."

Summary

When designing a framework, this is a good example of parent-child relationships and use cases being thought through carefully. Reading the source reveals design intent like this, so if you want to go deeper, read the code.

https://github.com/ionic-team/ionic-framework/blob/master/core/src/components/item/item.scss

See you again.