Styling

How to approach styling with Themes.

Introduction

Themes does not come with a built-in styling system. There’s no css or sx prop, and it does not use any styling libraries internally. Under the hood, it’s built with vanilla CSS.

There’s no overhead when it comes to picking a styling technology for your app.

What you get

The components in Themes are relatively closed—they come with a set of styles that aren’t always easily overridden. They are customizable within what’s allowed by their props and the theme configuration.

However, you also get access to the same CSS variables that power the Themes components. You can use these tokens to create custom components that naturally feel at home in the original theme. Changes to the token system are treated as breaking.

For more information on specific tokens, refer to the corresponding guides in the Theme section.

Color system

ABCD

ABCDEFG

ABCDEFGHI

ABCDEFGHIJ

ABCDEFGHIJKL

A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine. I am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now. When, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath

Ambiguous voice of a heart which prefers kiwi bowls to a zephyr.

Typography examples
Shadow and radius examples

Overriding styles

Beyond simple style overrides, we recommend using the components as-is, or create your own versions using the same building blocks.

Most components do have className and style props, but if you find yourself needing to override a lot of styles, it’s a good sign that you should either:

  • Try to achieve what you need with the existing props and theme configuration.
  • See whether you can achieve your design by tweaking the underlying token system.
  • Create your own component using lower-level building blocks, such Primitives and Colors.
  • Reconsider whether Themes is the right fit for your project.

Tailwind

Tailwind is great. Yet, if you plan to use Themes with Tailwind, keep in mind how its ergonomics may encourage you to create complex styles on the fly, sometimes reaching into the component internals without friction.

Tailwind is a different styling paradigm, which may not mix well with the idea of a closed component system where customization is achieved through props, tokens, and creating new components on top of a shared set of building blocks.

Custom components

If you need to create a custom component, use the same building blocks that Themes uses:

  • Themes tokens that power the components
  • Primitives, a library of accessible, unstyled components
  • Colors, a color system for building beautiful websites and apps

Feel free to explore the source code of Themes to see how it is built.

Common issues

z-index conflicts

Out of the box, portalled Themes components can be nested and stacked in any order without conflicts. For example, you can open a popover that opens a dialog, which in turn opens another popover. They all stack on top of each other in the order they were opened:

When building your own components, use the following rules to avoid z-index conflicts:

  • Don’t use z-index values other than auto, 0, or -1 in rare cases.
  • Render the elements that should stack on top of each other in portals.

Your main content and portalled content are separated by the stacking context that the styles of the root <Theme> component create. This allows you to stack portalled content on top of the main content without worrying about z-indices.

Next.js import order

As of Next.js 13.0 to 14.1, the import order of CSS files in app/**/layout.tsx is not guaranteed, so Themes may overwrite your own styles even when written correctly:

import "@teasim/themes/styles.css";
import "./my-styles.css";

This Next.js issue may come and go sporadically, or happen only in development or production.

As a workaround, you can merge all the CSS into a single file first via postcss-import and import just that into your layout. Alternatively, importing the styles directly in page.tsx files also works.

Tailwind base styles

As of Tailwind v3, styles produced by the @tailwind directive are usually appended after any imported CSS, no matter the original import order. In particular, Tailwind’s button reset style may interfere with Themes buttons, rendering certain buttons without a background color.

Workarounds:

  • Don’t use @tailwind base
  • Set up separate CSS layers for Tailwind and Themes
  • Set up postcss-import and manually import Tailwind base styles via @import tailwindcss/base before Themes styles.

Missing styles in portals

When you render a custom portal in a Themes project, it will naturally appear outside of the root <Theme> component, which means it won’t have access to most of the theme tokens and styles. To fix that, wrap the portal content with another <Theme>:

// Implementation example of a custom dialog using the low-level Dialog primitive
// Refer to https://tealess.com/primitives/docs/components/dialog
import * as Dialog from "@Teasim-ui/react-dialog";
import { Theme } from "@teasim/themes";
function MyCustomDialog() {
return (
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Theme>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Title />
<Dialog.Description />
<Dialog.Close />
</Dialog.Content>
</Theme>
</Dialog.Portal>
</Dialog.Root>
);
}

Components like Dialog and Popover in Themes already handle this for you, so this is only necessary when creating your own portalled components.

Complex CSS precedence

Usually, you’d want your custom CSS to override Themes styles. However, there are cases when it is natural to expect the opposite.

Consider a simple paragraph style that just resets the browser’s default margin:

.my-paragraph {
margin: 0;
}

You might apply the margin prop from a Box onto your custom paragraph via asChild:

import "@teasim/themes/styles.css";
import "./my-styles.css";
function MyApp() {
return (
<Theme>
<Box asChild m="5">
<p className="my-paragraph">My custom paragraph</p>
</Box>
</Theme>
);
}

Yet, this won’t work intuitively. The custom styles are imported after Themes styles, so they will override the margin prop. As a workaround, Themes provides separate tokens.css, components.css, and utilities.css files that the original styles.css is built upon:

import "@teasim/themes/tokens.css";
import "@teasim/themes/components.css";
import "@teasim/themes/utilities.css";

You can import utilities.css after your custom styles to ensure that the layout props work as expected with your custom styles. However, if you use Next.js, keep in mind the import order issue that’s mentioned above.

If you use standalone layout components, split CSS files are also available for them:

import "@teasim/themes/layout/tokens.css";
import "@teasim/themes/layout/components.css";
import "@teasim/themes/layout/utilities.css";