Skip to content
Lenso UI

Styling

Style HeroUI components with CSS, Tailwind, or CSS-in-JS

HeroUI components provide flexible styling options: Tailwind CSS utilities, CSS with BEM classes or data attributes, CSS-in-JS libraries, and render props for dynamic styling.

Basic Styling

Using className: All HeroUI components accept className props:

<Button className="bg-purple-500 hover:bg-purple-600">  Custom Button</Button>
<Accordion className="border-2 border-gray-200 rounded-xl">  {/* content */}</Accordion>

Using style: Components also accept inline styles:

<Button style={{ backgroundColor: '#8B5CF6' }}>  Styled Button</Button>

Scrollbars

HeroUI scroll slots use @apply scrollbar in component CSS. For your own overflow containers, use the utilities from @lenso/tokens:

<div className="scrollbar h-64 overflow-y-auto">  {/* long content */}</div>
UtilityEffect
scrollbarHeroUI thumb (reads theme --scrollbar-* variables)
scrollbar-thinHeroUI themed thin scrollbar
scrollbar-defaultOS / browser scrollbars
scrollbar-noneHidden scrollbar

Global and per-subtree control uses data-scrollbar on an ancestor. See Theming for tokens and modes.

State-Based Styling

HeroUI components expose their state through data attributes, similar to CSS pseudo-classes:

/* Target different states */.button[data-hovered="true"], .button:hover {  background: var(--accent-hover);}
.button[data-pressed="true"], .button:active {  transform: scale(0.97);}
.button[data-focus-visible="true"], .button:focus-visible {  outline: 2px solid var(--focus);}

Render Props

Apply dynamic styling based on component state:

// Dynamic classes<Button  className={({ isPressed }) =>    isPressed ? 'bg-blue-600' : 'bg-blue-500'  }>  Press me</Button>
// Dynamic content<Button>  {({ isHovered, isPressed }) => (    <>      <Icon        icon="gravity-ui:heart"        className={isPressed ? 'text-red-500' : 'text-neutral-400'}      />      <span className={isHovered ? 'underline' : ''}>        Like      </span>    </>  )}</Button>

BEM Classes

HeroUI uses BEM methodology for consistent class naming:

/* Block */.button { }.accordion { }
/* Element */.accordion__trigger { }.accordion__panel { }
/* Modifier */.button--primary { }.button--lg { }.accordion--outline { }

Customizing components globally:

/* global.css */
@layer components {  /* Override button styles */  .button {    @apply font-semibold uppercase;  }
  .button--primary {    @apply bg-indigo-600 hover:bg-indigo-700;  }
  /* Add custom variant */  .button--gradient {    @apply bg-gradient-to-r from-purple-500 to-pink-500;  }}

Creating Wrapper Components

Create reusable custom components using tailwind-variants—a Tailwind CSS first-class variant API:

import { Button as HeroButton, type ButtonProps } from '@lenso/ui';import { buttonVariants, tv, type VariantProps } from '@lenso/tokens';
const customButtonVariants = tv({  extend: buttonVariants,  base: 'font-medium transition-all',  variants: {    intent: {      primary: 'bg-blue-500 hover:bg-blue-600 text-white',      secondary: 'bg-gray-200 hover:bg-gray-300',      danger: 'bg-red-500 hover:bg-red-600 text-white',    },    size: {      small: 'text-sm px-2 py-1',      medium: 'text-base px-4 py-2',      large: 'text-lg px-6 py-3',    },  },  defaultVariants: {    intent: 'primary',    size: 'medium',  },});
type CustomButtonVariants = VariantProps<typeof customButtonVariants>;interface CustomButtonProps  extends Omit<ButtonProps, 'className'>,  CustomButtonVariants {  className?: string;}
export function CustomButton({ intent, size, className, ...props }: CustomButtonProps) {  return (    <HeroButton      className={customButtonVariants({ intent, size, className })}      {...props}    />  );}

CSS-in-JS Integration

Styled Components:

import styled from 'styled-components';import { Button } from '@lenso/ui';
const StyledButton = styled(Button)`  background: linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%);  border-radius: 8px;  color: white;  padding: 12px 24px;
  &:hover {    box-shadow: 0 3px 10px rgba(255, 105, 135, 0.3);  }`;

Emotion:

import { css } from '@emotion/css';import { Button } from '@lenso/ui';
const buttonStyles = css`  background: linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%);  border-radius: 8px;  color: white;  padding: 12px 24px;
  &:hover {    box-shadow: 0 3px 10px rgba(255, 105, 135, 0.3);  }`;
<Button className={buttonStyles}>  Emotion Button</Button>

Responsive Design

Using Tailwind utilities:

<Button className="text-sm md:text-base lg:text-lg px-3 md:px-4 lg:px-6">  Responsive Button</Button>

Or with CSS:

.button {  font-size: 0.875rem;  padding: 0.5rem 1rem;}
@media (min-width: 768px) {  .button {    font-size: 1rem;    padding: 0.75rem 1.5rem;  }}

CSS Modules

For scoped styles, use CSS Modules:

/* Button.module.css */.button {  background: linear-gradient(135deg, #667eea, #764ba2);  color: white;  padding: 12px 24px;  border-radius: 8px;}
.button:hover {  transform: translateY(-2px);}
.button--primary {  background: linear-gradient(135deg, #667eea, #764ba2);  color: white;  padding: 12px 24px;  border-radius: 8px;}
import styles from './Button.module.css';import { Button } from '@lenso/ui';
<Button className={styles.button}>  Scoped Button</Button>

Component Classes Reference

Button: .button, .button--{variant}, .button--{size}, .button--icon-only Accordion: .accordion, .accordion__item, .accordion__trigger, .accordion__panel, .accordion--outline

Note: See component docs for complete class references: Button, Accordion

View all component classes in @lenso/tokens/components.

Next Steps