Styling slots

Slots name the internal parts of a component (root, label, control, loader, and so on). You style those parts through classes, styles, and cssVariables without forking source or depending on a CSS framework runtime.

Overview

@sometic/styling resolves class names, inline styles, CSS variables, slot markers, and state attributes. Cores do not depend on Tailwind, Bootstrap, or CSS-in-JS libraries. You pass strings (or maps of strings); you own the CSS toolchain.

Common Styleable props on adapters:

PropRole
unstyledSkip default / variant class layers
classesPer-slot class values
stylesPer-slot inline styles
cssVariablesCustom properties on the root
defaults / variantsOptional default and variant layers
mergeOptional class merger (for example Tailwind conflict helpers)

Slot markers

Slots are identified with data-slot:

ts
import { createSlotAttributes, defineSlots } from "@sometic/styling/slots";

const BUTTON_SLOTS = defineSlots(["root", "prefix", "content", "suffix", "loader"] as const);

createSlotAttributes("content");
// { "data-slot": "content" }

Consumer CSS can target parts without brittle DOM depth:

css
[data-slot="root"] {
    display: inline-flex;
    gap: 0.5rem;
}

[data-slot="loader"] {
    inline-size: 1rem;
    block-size: 1rem;
}

Example: button slots (React)

tsx
import { Button } from "@sometic/react/button";

export function SaveButton(): JSX.Element {
    return (
        <Button
            classes={{
                root: "btn btn-primary",
                content: "btn__label",
                loader: "btn__spinner",
            }}
            styles={{
                root: { minInlineSize: "8rem" },
            }}
            cssVariables={{
                "--btn-radius": "0.5rem",
            }}
            loading={false}
        >
            Save
        </Button>
    );
}

Anatomy for Button (representative):

Partdata-slot
Rootroot
Prefixprefix
Contentcontent
Suffixsuffix
Loaderloader

Field and input families expose additional slots such as label, control, and nativeInput. See each component page for the authoritative map.

Override priority

Resolution order is deterministic (later layers win in the documented algorithm):

  1. Behavior-required styles (rare, justified a11y helpers)
  2. Default theme / token-derived values when present
  3. Component variant and size defaults
  4. State-driven classes/styles
  5. Consumer classes / styles
  6. Consumer cssVariables

unstyled: true: skips defaults and variants only. Behavior, state attributes, user overrides, and CSS variables still apply.

Working with Tailwind or Bootstrap

Pass utility or Bootstrap class names through classes. Sometic never imports those frameworks.

tsx
<Button
    classes={{ root: "px-3 py-2 rounded-md bg-slate-900 text-white" }}
    merge={yourTailwindMerge}
/>

Provide merge only when you need conflict resolution. There is no hard dependency on tailwind-merge.

See also Theming for token and CSS-variable workflows (plain-css, tailwind, bootstrap guides).

Polymorphic as

Foundation exposes resolvePolymorphicAs for cross-framework “render as” contracts. Framework-specific composition helpers (for example React asChild patterns) live in adapters when supported, not in styling core.

When to use slots vs global CSS

Use slot maps when one instance needs different part styling, or when you compose design-system recipes in JS.

Use attribute selectors ([data-slot], [data-disabled]) when you prefer stylesheet ownership and low JS surface.

Both can coexist. Prefer stable public hooks over reaching into private DOM structure.

FAQ

Do I need @sometic/theme to use slots? No. Slots and class resolvers stand alone. Theme adds tokens and CSS variable generation.

Where do design tokens live? @sometic/theme. Styling only merges consumer CSS variables you pass in. See Design tokens.

Can I style only with CSS? Yes. Target data-slot and state attributes.

Why not ship a single CSS framework? Product boundary: one behavior model, your styling system. See Architecture.