{"id":3800,"date":"2021-08-10T19:06:08","date_gmt":"2021-08-10T19:06:08","guid":{"rendered":"https:\/\/ionicframework.com\/blog\/?p=3800"},"modified":"2023-01-21T00:35:42","modified_gmt":"2023-01-21T05:35:42","slug":"building-with-stencil-tabs","status":"publish","type":"post","link":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs","title":{"rendered":"Building with Stencil: Tabs"},"content":{"rendered":"<p>As user interfaces (UI) go, tabs are a particularly useful pattern. The word &#8220;tab&#8221; invokes images of beige manila file folders. Early UI mimicked this physical property by placing buttons along the top of a dedicated space. Mobile devices, with their confined spaces, find the tab pattern in accordions, bottom button bars, and more.<\/p>\n<p>In this example, we will look at the mechanics of building a tab component using <a href=\"https:\/\/stenciljs.com\/\">Stencil<\/a>.<\/p>\n<p><!--more--><\/p>\n<h2>Composition<\/h2>\n<p>Let us talk for a moment about composition. How does a component magically grow a tab? In Web Components parlance, composition refers to the idea that a component can have other HTML elements, including components, inside of the opening and closing tags. What the parent component does with that content is up to it.<\/p>\n<pre><code class=\"language-html\">&lt;ionx-tabs&gt;\n  &lt;p&gt;Content 1&lt;\/p&gt;\n  &lt;p&gt;Content 2&lt;\/p&gt;\n  &lt;p&gt;Content 3&lt;\/p&gt;\n&lt;\/ionx-tabs&gt;\n<\/code><\/pre>\n<p>Given the above HTML snippet, in the case of a tab component, we would expect to have something like three buttons, each controlling the visibility of one child element at a time. Believe it or not, there is almost enough information here to make that happen. The only piece of information missing is what to label the buttons.<\/p>\n<h2>Data Attributes<\/h2>\n<p>One of the great features of HTML is how extensible it can be. One of the ways that HTML accomplishes this is through the use of data attributes. Data attributes have this name because you can use the word &#8220;data&#8221; followed by a hyphen, and then whatever word you want to use. As an attribute, like many other defined HTML element attributes, a data attribute can also be assigned a value.<\/p>\n<pre><code>&lt;ionx-tabs&gt;\n  &lt;p data-label=&quot;One&quot;&gt;Content 1&lt;\/p&gt;\n  &lt;p data-label=&quot;Two&quot;&gt;Content 2&lt;\/p&gt;\n  &lt;p data-label=&quot;Three&quot;&gt;Content 3&lt;\/p&gt;    \n&lt;\/ionx-tabs&gt;\n<\/code><\/pre>\n<blockquote><p>\n  A data attribute does not have to be assigned a value, in which case, it will be treated as a <code>boolean<\/code> value. CSS can also be applied using the CSS attribute selector such as <code>input[type=text] { ... }<\/code>. We will circle back to both of these concepts later in this example.<\/p><\/blockquote>\n<p>To work with data attributes from JavaScript, we have a few functions at our disposal. If the data attribute contains a value, we can use <code>getAttribute()<\/code> to get that value. Conversely, we can use <code>setAttribute()<\/code> to set that value. If we do not want the data attribute to be present, we can use <code>removeAttribute()<\/code>. In the case of a <code>boolean<\/code> value, we want to check if the attribute is present (true) or not (false). For this we can use <code>hasAttribute()<\/code>.<\/p>\n<p>From a markup perspective, this is enough to generate, and label, the buttons that control the visible content.<\/p>\n<h2>What Is A Tab?<\/h2>\n<p>In the above markup, we are considering any child element to be representative of a piece of content whose visibility is controlled by a button, which is called a &#8220;tab&#8221; by convention. We have already decided that a tab has a label, but there are other properties to consider as well.<\/p>\n<p>The first additional property would be if the tab is selected or not. This is important not only internally, to hide content that is not selected, but also for the purposes of styling. A selected tab should have a different appearance than a tab that is not selected.<\/p>\n<p>A selected tab should also look different from a disabled tab&#8230; Disabled&#8230; There is another property! Depending on the state of the application using the tabs, we may also want to disable specific tabs.<\/p>\n<pre><code class=\"language-ts\">export default class Tab {\n  disabled: boolean = false;\n  label: string;\n  selected: boolean;\n\n  constructor( \n    label: string, \n    selected: boolean = false, \n    disabled: boolean = false\n  ) {\n    this.label = label;\n    this.selected = selected;\n    this.disabled = disabled;\n  }\n}\n<\/code><\/pre>\n<p>Closely related to disabled is visibility, which we can consider another property of a tab. Your tabs might have icons, which could be considered another property of the tab. Maybe you want the ability to &#8220;pin&#8221; tabs. Whatever properties you find relevant to your tab implementation, bundle them up into a class. This class will allow us to refer logically to each tab and its related properties.<\/p>\n<h2>Component Properties<\/h2>\n<p>Now we have a good grasp of the markup that should represent our tab component, and we even have a logical representation of the individual tabs. Now we will turn to the implementation itself, starting with the component properties.<\/p>\n<p>In order to get access to the child elements (composed content), we will need a reference to the web component element itself. This is called the &#8220;host element&#8221; in Stencil. From a web component standards perspective <code>this<\/code> is the same as the host element. To get a reference to this host element in Stencil, the <code>@Element()<\/code> decorator is used. You can name the property whatever you want, but convention is &#8220;host&#8221;.<\/p>\n<pre><code class=\"language-ts\">@Element() host: HTMLElement;\n<\/code><\/pre>\n<p>We will also want the component to keep track of the state of the tabs, which means an <code>Array<\/code> of <code>Tabs<\/code>.<\/p>\n<pre><code class=\"language-ts\">@State() tabs: Array&lt;Tab&gt; = [];\n<\/code><\/pre>\n<blockquote><p>\n  From an object-oriented programming perspective <code>tabs<\/code> is a property of the component class. From a component perspective, it represents the state of the tabs, and is decorated appropriately. This is not to be confused with the properties decorated with <code>@Prop()<\/code>. These are properties, too, but are decorated to control how the component manifests itself to the developer.<\/p><\/blockquote>\n<p>Our component will also need to keep track of the selected tab. To keep things easy, we can use a zero-based index to represent selection.<\/p>\n<pre><code class=\"language-ts\">@Prop( {mutable: true, reflected: true} ) selectedIndex: number = 0;\n<\/code><\/pre>\n<p>The selected index will change when a tab is clicked. When a component modifies one of its properties directly, it must be marked as <code>mutable<\/code>. We will also want to let the developer programmatically control the selected tab, as well as determine which tab is selected. Both of these purposes are served by marking a property as <code>reflected<\/code>.<\/p>\n<h3>Slots<\/h3>\n<p>Inside of a web component template, the <code>slot<\/code> tag allows us to specify where child content should go within the context of the overall layout of the component itself. If you need to specify more than one designated area for child content, you can name the slots, and then use those names on the child elements. If you choose not to have a <code>slot<\/code> tag at all, then the child content will not be shown.<\/p>\n<pre><code class=\"language-html\">&lt;!-- In a component template --&gt;\n&lt;button&gt;My Button&lt;\/button&gt;\n&lt;slot name=&quot;label&quot;&gt;&lt;\/slot&gt;\n&lt;div&gt;\n  &lt;slot name=&quot;content&quot;&gt;&lt;\/slot&gt;\n&lt;\/div&gt;\n\n&lt;!-- In your HTML --&gt;\n&lt;my-component&gt;\n  &lt;p slot=&quot;label&quot;&gt;My Label&lt;\/p&gt;\n  &lt;img slot=&quot;content&quot; src=&quot;\/img\/stencil.svg&quot; \/&gt;\n&lt;\/my-component&gt;\n<\/code><\/pre>\n<h2>Slot Change<\/h2>\n<p>When the elements inside of a web component slot are changed, the component emits an internal <code>slotchange<\/code> event. Capturing this event allows a web component to take any special actions it needs to address the change. In the case of a tab control, we can leverage this event to know the component needs to evaluate the child content, and may in turn need to update the buttons representing the tabs.<\/p>\n<h2>Template<\/h2>\n<p>Within the tab component template, we need two distinct areas &#8211; one to hold the buttons which represent the tabs themselves, and one to hold the desired content to be rendered.<\/p>\n<pre><code class=\"language-ts\">render() {\n  return ( [\n    &lt;div&gt;\n      {this.tabs.map( ( tab: Tab, index: number ) =&gt;\n        &lt;button\n          onClick={() =&gt; this.selectedIndex = index}\n          {... {\n            &#039;data-selected&#039;: tab.selected,\n            &#039;disabled&#039;: tab.disabled\n          }}\n          title={tab.label}&gt;\n          {tab.label}\n        &lt;\/button&gt;\n      ) }\n    &lt;\/div&gt;,\n    &lt;div&gt;\n      &lt;slot onSlotchange={() =&gt; this.doSlotChange()}&gt;&lt;\/slot&gt;\n    &lt;\/div&gt;        \n  ] );\n}\n<\/code><\/pre>\n<p>When a tab button is clicked, the index of the selected button is assigned to the <code>selectedIndex<\/code> property. This will invoke a render. True to form, and in sticking with the data attributes, if a tab is selected, it will have the <code>data-selected<\/code> attribute. This is then picked up in the CSS to style the button accordingly. The same is true for any disabled tab, in which case the button gets a <code>disabled<\/code> attribute.<\/p>\n<pre><code class=\"language-css\">button[data-selected] {\n  background-color: #f4f4f4;\n  border-left: solid 1px transparent;\n  border-top: solid 2px #0f62fe;\n  color: #161616;\n  font-weight: 600;\n}\n\nbutton[disabled] {\n  background-color: #c6c6c6;\n  color: #8d8d8d;\n  cursor: not-allowed;\n}\n<\/code><\/pre>\n<p>As for the <code>div<\/code> holding the content (via a <code>slot<\/code>), when the <code>slotchange<\/code> event is fired, the <code>doSlotChange()<\/code> handler is called. The <code>doSlotChange()<\/code> handler evaluates the child elements and populates the <code>tabs<\/code> property accordingly. When the <code>tabs<\/code> property is changed, a render is invoked.<\/p>\n<pre><code class=\"language-ts\">doSlotChange() {\n  this.tabs = [];\n\n  for( let c: number = 0; c &lt; this.host.children.length; c++ ) {\n    const label: string = this.host.children[c].getAttribute( &#039;data-label&#039; );\n    const selected: boolean = this.selectedIndex === c ? true : false;\n    const disabled: boolean = this.host.children[c].hasAttribute( &#039;data-disabled&#039; );\n    this.tabs.push( new Tab( label, selected, disabled ) );      \n  }\n}\n<\/code><\/pre>\n<h2>Render<\/h2>\n<p>Up to this point, we have put a lot of focus on the buttons that represent the tabs of our component. We still have one last concern &#8211; the visibility of the content within our tab component. We want to make sure that the visible content stays in sync with the buttons that control that visibility. The best place to do this is a quick check-in the <code>componentWillRender()<\/code> lifecycle function.<\/p>\n<pre><code class=\"language-ts\">componentWillRender() {\n  for( let t: number = 0; t &lt; this.tabs.length; t++ ) {\n    if( t === this.selectedIndex ) {\n      this.host.children[t].setAttribute( &#039;data-selected&#039;, &#039;&#039; );\n      this.tabs[t].selected = true;\n    } else {\n      this.host.children[t].removeAttribute( &#039;data-selected&#039; );\n      this.tabs[t].selected = false;\n    }\n  }\n}\n<\/code><\/pre>\n<p>It might seem odd that assigning or removing a <code>data-selected<\/code> attribute on the children elements is what controls their visibility. This happens because within the context of web components, CSS has the <code>::slotted()<\/code> selector which allows you to target specific elements within a <code>slot<\/code>. In this case, any child element that does not have the <code>data-selected<\/code> attribute is hidden.<\/p>\n<pre><code class=\"language-css\">::slotted( :not( [data-selected] ) ) {\n  display: none;\n}\n<\/code><\/pre>\n<p>And with that, our tab component is complete! \ud83c\udf89<\/p>\n<p>The component itself is not that complex, but it does involve taking into account just about every aspect of building web components. You need to understand composable content, how that translates into slots inside of components, and then how that can be managed in CSS. You need to understand the lifecycle of a web component with <code>slotchange<\/code> and <code>componentWillRender()<\/code>. And you need to understand how we can extend HTML using data attributes, and they can be used in CSS. And now? You do!<\/p>\n<h2>\u270b But What About &#8230;<\/h2>\n<p><strong>Unordered List<\/strong><\/p>\n<p>I display the tab buttons as <code>button<\/code> elements inside of a <code>div<\/code> container element. Since the tabs represent a list of options, the <code>ul<\/code> element may seem like a better fit. If you had no other function in your tabs save to label content, then the <code>ul<\/code> element would certainly be a good choice.<\/p>\n<p>In this case, I am taking advantage of a special behavior of the <code>button<\/code> element in that it already has a <code>disabled<\/code> attribute. When the <code>disabled<\/code> attribute is present, the button does not emit a click event. Using the <code>button<\/code> element means I do not have to write additional code to manage the event listener.<\/p>\n<p>You might then in turn suggest putting a <code>button<\/code> element inside the <code>li<\/code> element as part of a <code>ul<\/code> element. At this point, I would suggest we are splitting semantic hairs on content that is hidden in the shadow DOM in the first place.<\/p>\n<p><strong>Logical Tabs<\/strong><\/p>\n<p>In this example, we used a <code>Tab<\/code> class to hold the properties that are related to what a tab should represent. If I were using plain JavaScript, and not TypeScript, then I would not even bother making the class in the first place, and just keep an <code>Array<\/code> of <code>Object<\/code>.<\/p>\n<p>However, a case can be made for going the other way as well &#8211; formalizing a <code>Tab<\/code> component, perhaps using it as the container.<\/p>\n<pre><code class=\"language-html\">&lt;my-tabs&gt;\n  &lt;my-tab label=&quot;One&quot;&gt;\n    &lt;p&gt;Content 1&lt;\/p&gt;\n  &lt;\/my-tab&gt;\n  &lt;my-tab label=&quot;Two&quot;&gt;\n    &lt;p&gt;Content 2&lt;\/p&gt;\n  &lt;\/my-tab&gt;\n  &lt;my-tab label=&quot;Three&quot; disabled&gt;\n    &lt;p&gt;Content 3&lt;\/p&gt;\n  &lt;\/my-tab&gt;    \n&lt;\/my-tabs&gt;\n<\/code><\/pre>\n<p><a href=\"https:\/\/ionicframework.com\/docs\/api\/tabs\">Ionic Framework<\/a> takes this approach. In fact, because of the myriad situations in which a truly generic tab component may be used, Ionic Framework actually breaks the structure down even further.<\/p>\n<p>This is good for Ionic Framework, but this example is not teaching how to use Ionic Framework tabs. Rather this example is about how to implement the baseline tab UI pattern for your own components. For this reason, I kept this example as spartan as possible.<\/p>\n<h2>Next Steps<\/h2>\n<p>Now that you have an understanding of all the moving parts of this pattern, maybe you will want to layer in some abstractions of your own. Maybe you will go the declarative route and add specific components for the tab buttons. Or perhaps you might decide to allow the developer to specify a renderer to use for those buttons. Maybe you will add animation between the tab contents. Maybe you will implement an accordion rather than tabs.<\/p>\n<p>Once you get the hang of the pattern, you will find uses for it in many places. The complete code for this example is in <a href=\"https:\/\/github.com\/krhoyt\/Ionic\/tree\/master\/tabs\">GitHub<\/a>, and there is a <a href=\"http:\/\/temp.kevinhoyt.com\/ionic\/tabs\/\">live demo<\/a> you can view as well.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As user interfaces (UI) go, tabs are a particularly useful pattern. The word &#8220;tab&#8221; invokes images of beige manila file folders. Early UI mimicked this physical property by placing buttons along the top of a dedicated space. Mobile devices, with their confined spaces, find the tab pattern in accordions, bottom button bars, and more. In [&hellip;]<\/p>\n","protected":false},"author":85,"featured_media":3801,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"publish_to_discourse":"1","publish_post_category":"21","wpdc_auto_publish_overridden":"","wpdc_topic_tags":"","wpdc_pin_topic":"","wpdc_pin_until":"","discourse_post_id":"530324","discourse_permalink":"https:\/\/forum.ionicframework.com\/t\/building-with-stencil-tabs\/213633","wpdc_publishing_response":"","wpdc_publishing_error":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[223,124],"tags":[140,76,82],"class_list":["post-3800","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-stencil","category-tutorials","tag-design-systems","tag-stencil","tag-web-components"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v23.0 (Yoast SEO v23.0) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Building with Stencil: Tabs - Ionic Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building with Stencil: Tabs\" \/>\n<meta property=\"og:description\" content=\"As user interfaces (UI) go, tabs are a particularly useful pattern. The word &#8220;tab&#8221; invokes images of beige manila file folders. Early UI mimicked this physical property by placing buttons along the top of a dedicated space. Mobile devices, with their confined spaces, find the tab pattern in accordions, bottom button bars, and more. In [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs\" \/>\n<meta property=\"og:site_name\" content=\"Ionic Blog\" \/>\n<meta property=\"article:published_time\" content=\"2021-08-10T19:06:08+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-01-21T05:35:42+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Kevin Hoyt\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:site\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Kevin Hoyt\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#article\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs\"},\"author\":{\"name\":\"Kevin Hoyt\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/5cf8e478fa22da64450a7292b5596c81\"},\"headline\":\"Building with Stencil: Tabs\",\"datePublished\":\"2021-08-10T19:06:08+00:00\",\"dateModified\":\"2023-01-21T05:35:42+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs\"},\"wordCount\":1779,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png\",\"keywords\":[\"Design Systems\",\"stencil\",\"web components\"],\"articleSection\":[\"Stencil\",\"Tutorials\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs\",\"url\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs\",\"name\":\"Building with Stencil: Tabs - Ionic Blog\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#primaryimage\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png\",\"datePublished\":\"2021-08-10T19:06:08+00:00\",\"dateModified\":\"2023-01-21T05:35:42+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#primaryimage\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png\",\"width\":1920,\"height\":1080},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/ionic.io\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building with Stencil: Tabs\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/ionic.io\/blog\/#website\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"name\":\"ionic.io\/blog\",\"description\":\"Build amazing native and progressive web apps with the web\",\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/ionic.io\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/ionic.io\/blog\/#organization\",\"name\":\"Ionic\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"width\":1920,\"height\":854,\"caption\":\"Ionic\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/ionicframework\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/5cf8e478fa22da64450a7292b5596c81\",\"name\":\"Kevin Hoyt\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/07\/2520666-150x150.jpg\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/07\/2520666-150x150.jpg\",\"caption\":\"Kevin Hoyt\"},\"url\":\"https:\/\/ionic.io\/blog\/author\/hoyt\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Building with Stencil: Tabs - Ionic Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs","og_locale":"en_US","og_type":"article","og_title":"Building with Stencil: Tabs","og_description":"As user interfaces (UI) go, tabs are a particularly useful pattern. The word &#8220;tab&#8221; invokes images of beige manila file folders. Early UI mimicked this physical property by placing buttons along the top of a dedicated space. Mobile devices, with their confined spaces, find the tab pattern in accordions, bottom button bars, and more. In [&hellip;]","og_url":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs","og_site_name":"Ionic Blog","article_published_time":"2021-08-10T19:06:08+00:00","article_modified_time":"2023-01-21T05:35:42+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png","type":"image\/png"}],"author":"Kevin Hoyt","twitter_card":"summary_large_image","twitter_creator":"@ionicframework","twitter_site":"@ionicframework","twitter_misc":{"Written by":"Kevin Hoyt","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#article","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs"},"author":{"name":"Kevin Hoyt","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/5cf8e478fa22da64450a7292b5596c81"},"headline":"Building with Stencil: Tabs","datePublished":"2021-08-10T19:06:08+00:00","dateModified":"2023-01-21T05:35:42+00:00","mainEntityOfPage":{"@id":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs"},"wordCount":1779,"commentCount":0,"publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"image":{"@id":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png","keywords":["Design Systems","stencil","web components"],"articleSection":["Stencil","Tutorials"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/ionic.io\/blog\/building-with-stencil-tabs#respond"]}]},{"@type":"WebPage","@id":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs","url":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs","name":"Building with Stencil: Tabs - Ionic Blog","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#primaryimage"},"image":{"@id":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png","datePublished":"2021-08-10T19:06:08+00:00","dateModified":"2023-01-21T05:35:42+00:00","breadcrumb":{"@id":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ionic.io\/blog\/building-with-stencil-tabs"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#primaryimage","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png","width":1920,"height":1080},{"@type":"BreadcrumbList","@id":"https:\/\/ionic.io\/blog\/building-with-stencil-tabs#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/ionic.io\/blog"},{"@type":"ListItem","position":2,"name":"Building with Stencil: Tabs"}]},{"@type":"WebSite","@id":"https:\/\/ionic.io\/blog\/#website","url":"https:\/\/ionic.io\/blog\/","name":"ionic.io\/blog","description":"Build amazing native and progressive web apps with the web","publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ionic.io\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/ionic.io\/blog\/#organization","name":"Ionic","url":"https:\/\/ionic.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","width":1920,"height":854,"caption":"Ionic"},"image":{"@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/ionicframework"]},{"@type":"Person","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/5cf8e478fa22da64450a7292b5596c81","name":"Kevin Hoyt","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/07\/2520666-150x150.jpg","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/07\/2520666-150x150.jpg","caption":"Kevin Hoyt"},"url":"https:\/\/ionic.io\/blog\/author\/hoyt"}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/08\/Image-from-iOS-1.png","_links":{"self":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/3800","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/users\/85"}],"replies":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/comments?post=3800"}],"version-history":[{"count":1,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/3800\/revisions"}],"predecessor-version":[{"id":4728,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/3800\/revisions\/4728"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media\/3801"}],"wp:attachment":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media?parent=3800"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/categories?post=3800"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/tags?post=3800"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}