{
  "$type": "site.standard.document",
  "canonicalUrl": "https://johnnyreilly.com/posts/text-first-mui-tabs",
  "description": "Learn how to use the MUI tabs component in a text first way that remains strongly typed.",
  "path": "/posts/text-first-mui-tabs",
  "publishedAt": "2024-03-20T00:00:00.000Z",
  "site": "at://did:plc:yy3apqjlms24kso7ahn7lbmb/site.standard.publication/3mova7c4nho2b",
  "tags": [
    "react",
    "mui"
  ],
  "textContent": "I love the Material-UI (MUI) library for React. Hand on heart, I'm not very good at making UIs that are attractive. So I always grab for something to paper over the cracks. MUI is awesome for that.\n\nOne of the components that I use frequently is the tabs component. However, I've found that it can be a little tricky to use in a \"text-first\" way, that also remains strongly typed. This post documents how to do just that!\n\n\n\nOfficial example\n\nWhat does the tabs component look like? Well, here's a screenshot of it in action:\n\nIt's very useful if you'd like your users to be able to switch between different views easily. The official MUI documentation provides an example of how to use the tabs component:\n\nThis example is great, but (personally) I find it a little hard to read. There's a direct relationship between the tabs and the tab panels, but it's not immediately obvious. When you see the 0 passed to a11yProps and the 0 passed to CustomTabPanel, it's not clear that they're related. And if the a11yProps function call was not present, it would be even less clear.\n\nI'd like to see the tabs and tab panels presented together in a more text-first way, that makes the relationship between tab and tab panel more apparent.\n\nText-first tabs\n\nThe code I'd like to see would look something like this:\n\nIn this code snippet, the tabs and tab panels have more of a linkage, in a text-first way. It's hopefully clear that the \"Item One\" Tab and the \"Item One\" CustomTabPanel are related.\n\nIn the code above, the customTabProps function is used to generate the props for the tabs (it's an evolution of the a11yProps function that handles accessibility props as well as all others). Meanwhile, the CustomTabPanel component is used to render the tab panels. The selectedTab state is used to keep track of the selected tab.\n\nHow does this work? And is it strongly typed? Let's find out.\n\nStrongly typed tabs\n\nYes, it's strongly typed! We achieve this by defining a mapping of tab text to tab index:\n\nSo \"Item One\" is 0, \"Item Two\" is 1, and \"Item Three\" is 2.\n\nWe then do some TypeScript magic to strongly type this. We use as const to tell TypeScript this is an immutable object. With that done we can then extract the keys and values from the object and use them to create the derived types TabText and TabIndex.\n\nTabText is the keys of the tabs object and TabIndex is the values. So TabText is \"Item One\" | \"Item Two\" | \"Item Three\" and TabIndex is 0 | 1 | 2. If we should subsequently amend the tabs object in our code, TypeScript will ensure that the TabText and TabIndex types are updated accordingly.\n\nWe can then use these types in our components:\n\nThen our final example code looks like this:\n\nNote how we use our TabIndex types to strongly type the selectedTab state and the handleChange function. And also how the TabText type is used to strongly type the tab prop in the CustomTabPanel component and the tab argument in the customTabProps function. With this in place, we cannot provide invalid tab text to the customTabProps function or the CustomTabPanel component. TypeScript would fight us every step of the way if we tried.\n\nConclusion\n\nSo now we have a strongly typed, text-first way to use the MUI tabs component. We've used TypeScript to ensure that our tabs and tab panels are related in a way that is clear and easy to understand. This approach makes our code more maintainable and easier to work with. The full code is below:\n\nIt's certainly more complicated than the official example (and this may well be why the official example is the way it is), but it matches my preferences.\n\nAs an aside, I'd like the code even more if I had the following instead of using Tab with customTabProps:\n\nI avoided that in this post because it would have made the example more complicated. But I think it would be a nice improvement.",
  "title": "Text-first MUI Tabs"
}