Skip to content

Instantly share code, notes, and snippets.

@erikvullings
Last active May 28, 2025 14:55
Show Gist options
  • Save erikvullings/fa5700d53749a313259c8bd58e537c5b to your computer and use it in GitHub Desktop.
Save erikvullings/fa5700d53749a313259c8bd58e537c5b to your computer and use it in GitHub Desktop.
Mithril tab pill component

TabPills

Create a tab-like experience using 'pills', based on this CodePen.

Example of tab pills in action

m(TabPills, {
  tabs: [
    {
      title: [
        m("img.unselectable", {
          src: physAgres,
          alt: "Physcial violence",
          width: 48,
          height: 48,
        }),
        "Physcial violence",
      ],
      content: "Content 1",
    },
    {
      title: [
        m("img", {
          src: bodyLang,
          alt: "Non-physical violence",
          width: 48,
          height: 48,
        }),
        "Non-physical violence",
      ],
      content: "Content 2",
    },
  ],
}),
.wrapper {
/* width: 500px; */
height: 100%;
background: #fff;
margin: 15px auto 0;
}
.wrapper .tabs_wrap {
padding: 20px;
/* border-bottom: 1px solid #ebedec; */
}
.wrapper .tabs_wrap ul {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.wrapper .tabs_wrap ul li {
/* width: 135px; */
text-align: center;
background: #e9ecf1;
border-right: 1px solid #c1c4c9;
padding: 13px 15px;
cursor: pointer;
-webkit-transition: all 0.2s ease;
-o-transition: all 0.2s ease;
transition: all 0.2s ease;
}
.wrapper .tabs_wrap ul li:first-child {
border-top-left-radius: 25px;
border-bottom-left-radius: 25px;
}
.wrapper .tabs_wrap ul li:last-child {
border-right: 0px;
border-top-right-radius: 25px;
border-bottom-right-radius: 25px;
}
.wrapper .tabs_wrap ul li:hover,
.wrapper .tabs_wrap ul li.active {
background: #7fc469;
color: #fff;
}
import m, { Attributes, FactoryComponent, Vnode } from "mithril";
import { uniqueId } from "mithril-materialized";
import "./tab-pills.css";
export interface ITabItem {
/** Title of the tab */
title: string | Vnode<any, any> | Array<string | Vnode<any, any>>;
/** Content to render: may be empty in case of a using the tab as a hyperlink. */
content?: string | Vnode<any, any> | Array<string | Vnode<any, any>>;
/** ID of the tab element. Default the title in lowercase */
id?: string;
/** If the tab should be active */
active?: boolean;
/** Only used in combination with a set target to make the tab act as a regular hyperlink. */
href?: string;
}
export interface TabPillAttrs extends Partial<M.TabsOptions>, Attributes {
id?: string;
/** Selected tab id */
selectedTabId?: string;
/** List of tab items */
tabs: ITabItem[];
}
export const TabPills: FactoryComponent<TabPillAttrs> = () => {
let elId: string;
let selectedIndex: number = 0;
return {
oninit: ({ attrs: { id } }) => {
elId = id ?? uniqueId();
},
view: ({ attrs: { tabs = [], selectedTabId } }) => {
if (selectedTabId) {
const found = tabs.findIndex((tab) => tab.id === selectedTabId);
if (found >= 0) selectedIndex = found;
} else {
const found = tabs
.map((tab, index) => ({ tab, index }))
.filter((i) => i.tab);
if (found.length === 1) selectedIndex = found[0].index;
}
return m(
".wrapper",
m(
".tabs_wrap",
m(
"ul",
tabs.map((tab, index) =>
m(
"li",
{
"data-tabs": tab.id || elId + "-" + index,
className: selectedIndex === index ? "active" : undefined,
onclick: () => {
selectedIndex = index;
},
},
tab.title
)
)
)
),
selectedIndex >= 0 && selectedIndex < tabs.length
? tabs[selectedIndex].content
: undefined
);
},
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment