Creating Squishy Tabs With CSS Flexbox In Angular 9.1.7
At work, my team is starting to ideate on a new user interface (UI) that has a tabbed navigation. The set of tabs in this navigation is going to be driven by user-provided content and preferences. Which means, the number of tabs and the size of those tabs is going to be variable from user to user. As a fun little coding exercise, I wanted to see if I could use CSS Flexbox to gracefully handle a dynamic set of tabs, much like the browser does, in Angular 9.1.7.
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
While I try to keep my browser tabs to a reasonable count, I definitely have those day where I end up with a hundred tabs open; and, each tab is represented by little more than a "nub" in the user interface. What's great is that the browser just "makes it work", shrinking tabs down to the smallest possible size. And then, as you begin closing tabs, the browser allows the remaining tabs to expand, taking up more room.
CSS Flexbox, which is basically the greatest thing since sliced bread, is built for exactly this kind of functionality. It's literally right there in the name, "flex" - it's for building flexible layouts.
To create our flexible, squishy tab-set, all we have to do is set the following flex
properties:
flex-grow: 0
- Tells the tabs not grow beyond their content.flex-shrink: 1
- Tells the tabs that they can shrink.
Or, using the recommended short-hand, flex: 0 1 auto
. And, at this point, it's just like magic - it works!
To see this in action, I set up a simple App component in Angular that renders a list of tabs as a UL
. You can add new tabs to the list using an input
. And, you can "lock" a tab in place by clicking on it. Locking the tab adds a CSS class which sets the flex-shrink
property to 0
, telling the browser not to shrink the given tab.
// Import the core angular services.
import { Component } from "@angular/core";
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
interface Tab {
name: string;
isLocked: boolean;
}
@Component({
selector: "app-root",
styleUrls: [ "./app.component.less" ],
template:
`
<ul class="tabs">
<li
*ngFor="let tab of tabs"
[title]="tab.name"
class="tab"
[class.tab--locked]="tab.isLocked"
(click)="toggleLock( tab )">
{{ tab.name }}
</li>
</ul>
<p class="new-tab">
<input
#newTab
type="text"
autofocus
(keydown.Meta.Enter)="addTab( newTab )"
(keydown.Enter)="addTab( newTab )"
/>
<button (click)="addTab( newTab )">
Add Tab
</button>
</p>
`
})
export class AppComponent {
public tabs: Tab[];
// I initialize the app component.
constructor() {
this.tabs = [
{ name: "Home", isLocked: false },
{ name: "Favorites", isLocked: false },
{ name: "ToDos", isLocked: false },
{ name: "Articles To Read", isLocked: false },
{ name: "Random Thoughts", isLocked: false }
];
}
// ---
// PUBLIC METHODS.
// ---
// I add a new tab to the tab-list.
public addTab( input: HTMLInputElement ) : void {
var name = input.value.trim();
if ( name ) {
this.tabs.push({
name: input.value,
isLocked: false
});
input.value = "";
}
}
// I toggle the locking of the tab (ie, whether or not it can shrink).
public toggleLock( tab: Tab ) : void {
tab.isLocked = ! tab.isLocked;
}
}
Very little going on here - we're just rending the UL
of tabs. And, to see the Flexbox styling, here's the LESS CSS file. I've broken the LESS CSS file into two parts: one that shows the CSS Flexbox properties, and then one that does the visual styling for the demo.
:host {
display: block ;
font-size: 18px ;
}
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
// These styles are related to the flexbox functionality.
ul.tabs {
display: flex ;
}
li.tab {
// These are the properties which allow the tabs to SHRINK as space is needed,
// clipping the text and showing the overflow ellipsis.
flex: 0 1 auto ; // flex-grow: 0, flex-shrink: 1.
overflow: hidden ;
text-overflow: ellipsis ;
white-space: nowrap ;
// Then, when a tab is "locked", all we're doing it setting the flex-shrink to 0,
// telling the browser that this tab has to fit to the width of its content.
&--locked {
flex-shrink: 0 ;
}
}
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
// These styles are just for styling in the demo.
ul.tabs {
border-bottom: 2px solid #cccccc ;
list-style-type: none ;
margin: 30px 0px 20px 0px ;
padding: 0px 0px 0px 0px ;
}
li.tab {
background-color: #f0f0f0 ;
border-radius: 4px 4px 0px 0px ;
color: #333333 ;
cursor: pointer ;
font-family: monospace, sans-serif ;
margin: 0px 4px 0px 0px ;
padding: 5px 15px 4px 15px ;
&--locked {
background-color: #aad0e2 ;
}
}
p.new-tab {
display: inline-flex ;
input,
button {
font-size: inherit ;
margin-right: 7px ;
}
}
If you focus just on the CSS Flexbox part of this LESS file, there's very little going on. Like I said above, all we're doing is setting the flex-shrink
to 1
so the tabs can shrink down. And then, setting it to 0
when we want to lock a tab in place. And, when we run this Angular app in the browser, we get the following behavior:
It just works!
CSS Flexbox is a big bowl of awesome! I know that people love their CSS Grid; but for me, Flexbox revolutionized the way that I think about building user interfaces. I just love how easy it makes things with so little code.
Want to use code from this post? Check out the license.
Reader Comments