Component theming and styling

Custom components v2 provides seamless integration with Streamlit's theming system, allowing your components to automatically adapt to different themes, including dark and light modes. This integration is achieved through CSS Custom Properties that expose Streamlit's theme values directly to your component styles.

Streamlit automatically injects CSS Custom Properties into a wrapper element around your component instance. These properties are derived from the current Streamlit theme and are prefixed with --st- for easy identification. These values respect the viewer's theme selection, so you don't need separate logic to handle light and dark modes.

Reference Streamlit theme values in your component styles using the var() CSS function. If your component has an HTML element with the class my-component, the following CSS will use the following theme values:

  • --st-text-color for theme.textColor
  • --st-background-color for theme.backgroundColor
  • --st-border-color for theme.borderColor
  • --st-font for theme.font
CSS
.my-component {
  color: var(--st-text-color);
  background: var(--st-background-color);
  border: 1px solid var(--st-border-color);
  font-family: var(--st-font);
}

If your component is mounted in the sidebar, these values will correctly inherit from theme.sidebar.

In general, for any theme configuration option, use the CSS custom property --st-<option-name> to reference the value. <option-name> is the name of the option in the theme configuration in dash-case, also known as kebab-case.

For example, to reference the primary color (theme.primaryColor), use --st-primary-color. To reference the background color (theme.backgroundColor), use --st-background-color. For a description of all theme configuration options, see the config.toml API reference.

If a theme value is not configured, the CSS Custom Properties will have a valid value inherited from the current base theme.

There are a few computed CSS Custom Properties that don't come directly from a theme configuration option. The following CSS Custom Properties are computed:

CSS Custom PropertyUsed for
--st-heading-colorHeading font color (placeholder); same as text color
--st-border-color-lightLighter border color for stale or deactivated elements
--st-widget-border-colorWidget borders (when theme.showWidgetBorder is true)

Some theme properties are arrays. These are exposed as comma-separated strings. You can parse these in JavaScript if needed for dynamic styling.

CSS Custom PropertyUsed for
--st-heading-font-sizestheme.headingFontSizes
--st-heading-font-weightstheme.headingFontWeights
--st-chart-categorical-colorstheme.chartCategoricalColors
--st-chart-sequential-colorstheme.chartSequentialColors
--st-chart-diverging-colorstheme.chartDivergingColors

Heading font sizes and weights are also available as individual CSS Custom Properties for each heading level (1–6), so you don't need to parse the arrays when styling specific headings:

CSS Custom PropertyUsed for
--st-heading-font-size-1theme.headingFontSizes[0]
--st-heading-font-size-2theme.headingFontSizes[1]
--st-heading-font-size-3theme.headingFontSizes[2]
--st-heading-font-size-4theme.headingFontSizes[3]
--st-heading-font-size-5theme.headingFontSizes[4]
--st-heading-font-size-6theme.headingFontSizes[5]
--st-heading-font-weight-1theme.headingFontWeights[0]
--st-heading-font-weight-2theme.headingFontWeights[1]
--st-heading-font-weight-3theme.headingFontWeights[2]
--st-heading-font-weight-4theme.headingFontWeights[3]
--st-heading-font-weight-5theme.headingFontWeights[4]
--st-heading-font-weight-6theme.headingFontWeights[5]

The rest of the CSS Custom Properties are directly mapped to theme configuration options and are usable without parsing or modification:

CSS Custom Propertyconfig.toml theme option
--st-primary-colortheme.primaryColor
--st-background-colortheme.backgroundColor
--st-secondary-background-colortheme.secondaryBackgroundColor
--st-text-colortheme.textColor
--st-link-colortheme.linkColor
--st-link-underlinetheme.linkUnderline
--st-heading-fonttheme.headingFont
--st-code-fonttheme.codeFont
--st-base-radiustheme.baseRadius
--st-button-radiustheme.buttonRadius
--st-base-font-sizetheme.baseFontSize
--st-base-font-weighttheme.baseFontWeight
--st-code-font-weighttheme.codeFontWeight
--st-code-font-sizetheme.codeFontSize
--st-code-text-colortheme.codeTextColor
--st-border-colortheme.borderColor
--st-dataframe-border-colortheme.dataframeBorderColor
--st-dataframe-header-background-colortheme.dataframeHeaderBackgroundColor
--st-code-background-colortheme.codeBackgroundColor
--st-fonttheme.font
--st-red-colortheme.redColor
--st-orange-colortheme.orangeColor
--st-yellow-colortheme.yellowColor
--st-blue-colortheme.blueColor
--st-green-colortheme.greenColor
--st-violet-colortheme.violetColor
--st-gray-colortheme.grayColor
--st-red-background-colortheme.redBackgroundColor
--st-orange-background-colortheme.orangeBackgroundColor
--st-yellow-background-colortheme.yellowBackgroundColor
--st-blue-background-colortheme.blueBackgroundColor
--st-green-background-colortheme.greenBackgroundColor
--st-violet-background-colortheme.violetBackgroundColor
--st-gray-background-colortheme.grayBackgroundColor
--st-red-text-colortheme.redTextColor
--st-orange-text-colortheme.orangeTextColor
--st-yellow-text-colortheme.yellowTextColor
--st-blue-text-colortheme.blueTextColor
--st-green-text-colortheme.greenTextColor
--st-violet-text-colortheme.violetTextColor
--st-gray-text-colortheme.grayTextColor
--st-metric-value-font-sizetheme.metricValueFontSize
--st-metric-value-font-weighttheme.metricValueFontWeight

Here's a simple component that uses Streamlit's theming. Instead of using pixels for spacing, the component uses rem values. This ensures that the component will adjust to different font sizes. The font family and size are set on the parent container so they can be inherited by other elements. Execeptions like headers are styled in later lines. In genral, set colors, borders, border radii, and fonts from CSS Custom Properties.

Python
import streamlit as st

themed_card = st.components.v2.component(
    name="themed_card",
    html="""
    <div class="card">
        <h3 class="card-title">Themed Card</h3>
        <p class="card-content">
            This card automatically adapts to Streamlit's current theme.
        </p>
        <button class="card-button">Action</button>
    </div>
    """,
    css="""
    .card {
        background: var(--st-secondary-background-color);
        border: 1px solid var(--st-border-color);
        border-radius: var(--st-base-radius);
        padding: 1.25rem;
        margin: 0.625rem 0;
        font-family: var(--st-font);
        font-family: var(--st-font);
        font-size: var(--st-base-font-size);
    }

    .card-title {
        color: var(--st-heading-color);
        font-family: var(--st-heading-font);
        font-size: 1.2em;
        margin: 0 0 0.625rem 0;
        font-weight: 600;
    }

    .card-content {
        color: var(--st-text-color);
        line-height: 1.5;
        margin: 0 0 15px 0;
    }

    .card-button {
        background: var(--st-primary-color);
        color: white;
        border: none;
        border-radius: var(--st-button-radius);
        padding: 0.5rem 1rem;
        cursor: pointer;
        transition: opacity 0.2s;
    }

    .card-button:hover {
        opacity: 0.8;
    }
    """,
    js="""
    export default function({ parentElement, setTriggerValue }) {
        const cardButton = parentElement.querySelector('.card-button');
        cardButton.onclick = () => {
            setTriggerValue('button_click', 'clicked');
        };
    }
    """
)

result = themed_card(key="themed_example", on_button_click_change=lambda: None)
if result.button_click:
    st.write("Card button clicked!")

The following example demonstrates using Streamlit's basic color palette to set semantic colors. This is a component that creates color-coded alert banners:

Python
import streamlit as st

status_component = st.components.v2.component(
    name="status_message",
    html="""
    <div class="status" id="status-container">
        <span class="icon" id="icon"></span>
        <span class="message" id="message"></span>
    </div>
    """,
    css="""
    .status {
        display: flex;
        align-items: center;
        padding: 0.75rem 1rem;
        margin: 0.5rem 0;
        border-radius: var(--st-base-radius);
        border-left: 0.25rem solid;
        font-family: var(--st-font);
    }

    .status.success {
        background: var(--st-green-background-color);
        border-left-color: var(--st-green-color);
        color: var(--st-text-color);
    }

    .status.warning {
        background: var(--st-yellow-background-color);
        border-left-color: var(--st-yellow-color);
        color: var(--st-text-color);
    }

    .status.error {
        background: var(--st-red-background-color);
        border-left-color: var(--st-red-color);
        color: var(--st-text-color);
    }

    .status.info {
        background: var(--st-blue-background-color);
        border-left-color: var(--st-blue-color);
        color: var(--st-text-color);
    }

    .icon {
        margin-right: 0.625rem;
        font-size: 1rem;
    }

    .message {
        flex: 1;
        font-size: var(--st-base-font-size);
    }
    """,
    js="""
    export default function({ parentElement, data }) {
        const container = parentElement.querySelector('#status-container');
        const icon = parentElement.querySelector('#icon');
        const message = parentElement.querySelector('#message');

        // Set the status type class
        container.className = `status ${data.type}`;

        // Set the icon based on type
        const icons = {
            success: '✅',
            warning: '⚠️',
            error: '❌',
            info: 'ℹ️'
        };

        icon.textContent = icons[data.type] || '•';
        message.textContent = data.message;
    }
    """
)

# Mount the component four times with different status types
status_component(
    data={"type": "success", "message": "Operation completed successfully"},
    key="status_success"
)

status_component(
    data={"type": "warning", "message": "Please review your settings"},
    key="status_warning"
)

status_component(
    data={"type": "error", "message": "An error occurred during processing"},
    key="status_error"
)

status_component(
    data={"type": "info", "message": "Additional information available"},
    key="status_info"
)

You can use CSS Custom Properties to style a data table to match Streamlit's dataframe styling.

Python
import streamlit as st

data_table = st.components.v2.component(
    name="custom_table",
    html="""
    <div class="table-container">
        <table class="data-table">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Value</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>Item 1</td>
                    <td>100</td>
                    <td><span class="status-badge success">Active</span></td>
                </tr>
                <tr>
                    <td>Item 2</td>
                    <td>250</td>
                    <td><span class="status-badge warning">Pending</span></td>
                </tr>
                <tr>
                    <td>Item 3</td>
                    <td>75</td>
                    <td><span class="status-badge error">Inactive</span></td>
                </tr>
            </tbody>
        </table>
    </div>
    """,
    css="""
    .table-container {
        font-family: var(--st-font);
        overflow-x: auto;
    }

    .data-table {
        width: 100%;
        border-collapse: collapse;
        background: var(--st-background-color);
        border: 1px solid var(--st-dataframe-border-color);
        border-radius: var(--st-base-radius);
        overflow: hidden;
    }

    .data-table th {
        background: var(--st-dataframe-header-background-color);
        color: var(--st-text-color);
        font-weight: 600;
        padding: 0.75rem 1rem;
        text-align: left;
        border-bottom: 1px solid var(--st-dataframe-border-color);
        font-size: var(--st-base-font-size);
    }

    .data-table td {
        padding: 0.75rem 1rem;
        border-bottom: 1px solid var(--st-dataframe-border-color);
        color: var(--st-text-color);
        font-size: var(--st-base-font-size);
    }

    .data-table tr:last-child td {
        border-bottom: none;
    }

    .data-table tr:hover {
        background: var(--st-secondary-background-color);
    }

    .status-badge {
        padding: 0.25rem 0.5rem;
        border-radius: calc(var(--st-base-radius) / 2);
        font-size: 0.75rem;
        font-weight: 500;
    }

    .status-badge.success {
        background: var(--st-green-background-color);
        color: var(--st-green-color);
    }

    .status-badge.warning {
        background: var(--st-yellow-background-color);
        color: var(--st-yellow-color);
    }

    .status-badge.error {
        background: var(--st-red-background-color);
        color: var(--st-red-color);
    }
    """
)

result = data_table(key="table_example")

Custom components v2 provides style isolation options to control how your component styles interact with the rest of the page.

By default, Streamlit sets isolate_styles=True when registering a component, which wraps the instance in a Shadow DOM:

Python
my_component = st.components.v2.component(
    name="isolated_component",
    html="<div class='my-style'>Isolated content</div>",
    css=".my-style { color: red; }",
    isolate_styles=True
)

Benefits of isolation:

  • Component styles won't leak to the rest of the page.
  • Page styles won't interfere with your component.
  • Safer for third-party components.

If you want your component's style to affect the rest of the page, you can set isolate_styles=False when mounting. This is uncommon.

Python
# Styles can affect the page
non_isolated_component = st.components.v2.component(
    name="non_isolated_component",
    html="<div class='inherits-styles'>Content with inheritance</div>",
    css=".inherits-styles { font-family: inherit; }",  # Inherits page fonts
    isolate_styles=False
)

Create components that work well across different screen sizes. This makes your component more accessible and compatible with the Streamlit layout system. The following example uses @media (max-width: 768px) to create a responsive grid layout that adapts when the screen width is less than 768px.

Python
import streamlit as st

responsive_component = st.components.v2.component(
    name="responsive_layout",
    html="""
    <div class="responsive-grid">
        <div class="grid-item">Item 1</div>
        <div class="grid-item">Item 2</div>
        <div class="grid-item">Item 3</div>
        <div class="grid-item">Item 4</div>
    </div>
    """,
    css="""
    .responsive-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
        gap: 1rem;
        padding: 1rem;
        font-family: var(--st-font);
    }

    .grid-item {
        background: var(--st-secondary-background-color);
        border: 1px solid var(--st-border-color);
        border-radius: var(--st-base-radius);
        padding: 1.25rem;
        text-align: center;
        color: var(--st-text-color);
        transition: transform 0.2s;
    }

    .grid-item:hover {
        transform: translateY(-2px);
        box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
    }

    /* Mobile-specific styles */
    @media (max-width: 768px) {
        .responsive-grid {
            grid-template-columns: 1fr;
            gap: 0.75rem;
            padding: 0.75rem;
        }

        .grid-item {
            padding: 1rem;
        }
    }
    """
)

responsive_component(key="responsive_example")

Instead of hardcoding colors, always use Streamlit's theme variables:

CSS
/* Don't do this */
.my-component {
  color: #262730;
  background: #ffffff;
}

/* Do this instead */
.my-component {
  color: var(--st-text-color);
  background: var(--st-background-color);
}

Always test your components in both light and dark base themes. Preferably, test your component with a custom theme as well, especially using different font sizes.

Choose colors from the basic color palette based on their semantic meaning. Each color in the basic color palette has a text and background variation, in addition to its base color.

CSS
/* Good - semantic usage */
.error-message {
  color: var(--st-red-text-color);
  background: var(--st-red-background-color);
}

.success-indicator {
  color: var(--st-green-color);
}

Streamlit's theme colors are designed with accessibility in mind. Maintain proper contrast ratios when creating custom color combinations.

Now that you understand theming and styling:

forum

Still have questions?

Our forums are full of helpful information and Streamlit experts.