Theme
Version
GitHub PyPI Discord
On this page

Split Button

Use CSplitButton when one action is clearly dominant and a short Menu holds closely related alternatives. The primary and Menu trigger are separate native Buttons with separate names and Tab stops.

Use CButton for one action, CButtonGroup for visible peers, and CMenu when there is no dominant action. Use CSelect or CCombobox when the reader is choosing a value rather than running an action.

Related guidance: Button, Button Group, Menu, the WAI-ARIA APG Menu Button pattern, and the native Button element.

Split Button at a glance

Save the specimen directly or open related save actions. The Menu does not repeat the dominant Save action.

Split Button at a glance
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SplitButtonAtAGlance(Component):
    template = """
      <section
        class="split-button-glance"
        x-data="{saved:0,last:'No action yet'}"
      >
        <p class="split-button-glance__eyebrow">Field journal</p>
        <h2>Alpine gentian specimen</h2>
        <p>Keep the primary save action visible and related work nearby.</p>
        <c-CSplitButton
          label="Save specimen actions"
          menu_label="More save specimen actions"
          c-primary_attrs="{'@click':'saved += 1; last = `Saved specimen ${saved}`'}"
          $c-props="{onAction:(value)=>last=value}"
        >
          <c-fill name="default">Save specimen</c-fill>
          <c-fill name="menu">
            <c-CMenuItem value="Save a copy">Save a copy</c-CMenuItem>
            <c-CMenuItem value="Export record">Export record</c-CMenuItem>
            <c-CMenuItem value="Archive specimen" intent="danger">
              Archive specimen
            </c-CMenuItem>
          </c-fill>
        </c-CSplitButton>
        <output x-text="last">No action yet</output>
      </section>
    """

    css = """
      :where(.split-button-glance) {
        display: grid;
        gap: 0.75rem;
        justify-items: start;
        min-block-size: 18rem;
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }

      :where(.split-button-glance h2, .split-button-glance p) {
        margin: 0;
      }

      :where(.split-button-glance__eyebrow) {
        color: light-dark(#3f6b42, #9ed5a1);
        font-size: 0.75rem;
        font-weight: 700;
        letter-spacing: 0.08em;
        text-transform: uppercase;
      }
    """


preview = SplitButtonAtAGlance()

preview  # noqa: B018

Compose the two actions

Supply a nonempty group label, a specific menu_label, visible primary content, and at least one existing Menu declaration.

<c-CSplitButton
  label="Save specimen actions"
  menu_label="More save specimen actions"
>
  <c-fill name="default">Save specimen</c-fill>
  <c-fill name="menu">
    <c-CMenuItem value="save-copy">Save a copy</c-CMenuItem>
    <c-CMenuItem value="export">Export record</c-CMenuItem>
  </c-fill>
</c-CSplitButton>

Direct Python composition uses the same slots and public Menu declarations:

from citry_ui import CMenuItem, CSplitButton

save_actions = CSplitButton(
    label="Save specimen actions",
    menu_label="More save specimen actions",
    slots={
        "default": "Save specimen",
        "menu": (
            CMenuItem(
                value="save-copy",
                slots={"default": "Save a copy"},
            ),
            CMenuItem(
                value="export",
                slots={"default": "Export record"},
            ),
        ),
    },
)

The next example renders both composition forms.

Template and Python composition
Show code
from typing import Any

import citry_ui
from citry import Component, citry
from citry_ui import CMenuItem, CSplitButton

citry.register_library(citry_ui)


class BasicSplitButtonActions(Component):
    class Kwargs:
        pass

    class Slots:
        pass

    def template_data(
        self,
        kwargs: Kwargs,  # noqa: ARG002
        slots: Slots,  # noqa: ARG002
    ) -> dict[str, Any]:
        return {
            "python_split_button": CSplitButton(
                label="Publish specimen actions",
                menu_label="More publish specimen actions",
                variant="outline",
                slots={
                    "default": "Publish specimen",
                    "menu": (
                        CMenuItem(
                            value="preview",
                            slots={"default": "Preview publication"},
                        ),
                        CMenuItem(
                            value="schedule",
                            slots={"default": "Schedule publication"},
                        ),
                    ),
                },
            )
        }

    template = """
      <section class="split-button-basic">
        <article>
          <p>Template composition</p>
          <c-CSplitButton
            label="Save specimen actions"
            menu_label="More save specimen actions"
          >
            <c-fill name="default">Save specimen</c-fill>
            <c-fill name="menu">
              <c-CMenuItem value="save-copy">Save a copy</c-CMenuItem>
              <c-CMenuItem value="export">Export record</c-CMenuItem>
            </c-fill>
          </c-CSplitButton>
        </article>
        <article>
          <p>Python composition</p>
          {{ python_split_button }}
        </article>
      </section>
    """

    css = """
      :where(.split-button-basic) {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
        gap: 1rem;
        min-block-size: 17rem;
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }

      :where(.split-button-basic article) {
        display: grid;
        gap: 0.75rem;
        align-content: start;
        padding: 1rem;
        border: 1px solid color-mix(in srgb, currentColor 20%, transparent);
        border-radius: 0.75rem;
      }

      :where(.split-button-basic p) {
        margin: 0;
        font-weight: 700;
      }
    """


preview = BasicSplitButtonActions()

preview  # noqa: B018

Submit and reset native Forms

Only the primary Button participates in a Form. Set type="submit" or type="reset", then pass name, value, form, and submitter overrides through primary_attrs. The Menu Button and Menu items never submit.

Submit, reset, and related export actions
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SplitButtonForms(Component):
    template = """
      <section
        class="split-button-forms"
        x-data="{result:'No Form action yet',owner:'accession-form'}"
      >
        <form
          id="accession-form"
          x-ref="accession"
          @submit.prevent="
            result = `Submitted ${new FormData($event.target, $event.submitter).get('action')}`
          "
          @reset="setTimeout(() => result = 'Reset accession', 0)"
        >
          <label>
            Accession name
            <input name="specimen" value="Alpine gentian" required />
          </label>
          <div class="split-button-forms__actions">
            <c-CSplitButton
              label="Commit accession actions"
              menu_label="More commit accession actions"
              type="submit"
              c-primary_attrs="{'name':'action','value':'commit'}"
            >
              <c-fill name="default">Commit accession</c-fill>
              <c-fill name="menu">
                <c-CMenuItem value="export-draft">
                  Export draft
                </c-CMenuItem>
              </c-fill>
            </c-CSplitButton>
            <c-CSplitButton
              label="Reset accession actions"
              menu_label="More reset accession actions"
              type="reset"
              variant="outline"
            >
              <c-fill name="default">Reset accession</c-fill>
              <c-fill name="menu">
                <c-CMenuItem value="restore-snapshot">
                  Restore saved snapshot
                </c-CMenuItem>
              </c-fill>
            </c-CSplitButton>
          </div>
        </form>

        <form
          id="secondary-accession-form"
          @submit.prevent="
            result = `Submitted to secondary Form with ${$event.submitter.value}`
          "
        >
          <label>
            Secondary accession
            <input name="secondary-specimen" value="Sea thrift" required />
          </label>
        </form>

        <label>
          External primary Form owner
          <select x-model="owner">
            <option value="accession-form">Main accession Form</option>
            <option value="secondary-accession-form">Secondary accession Form</option>
          </select>
        </label>

        <c-CSplitButton
          id="external-commit-actions"
          label="External commit actions"
          menu_label="More external commit actions"
          type="submit"
          size="sm"
          c-primary_attrs="{
            ':form':'owner',
            'name':'action',
            'value':'external-commit'
          }"
        >
          <c-fill name="default">Commit from outside</c-fill>
          <c-fill name="menu">
            <c-CMenuItem value="download">Download draft</c-CMenuItem>
          </c-fill>
        </c-CSplitButton>

        <button
          type="button"
          @click="
            document.getElementById(owner).requestSubmit(
              document.getElementById('external-commit-actions-primary')
            )
          "
        >
          Request native submit
        </button>
        <output x-text="result">No Form action yet</output>
      </section>
    """

    css = """
      :where(.split-button-forms) {
        display: grid;
        gap: 1rem;
        max-inline-size: 38rem;
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }

      :where(.split-button-forms form, .split-button-forms label) {
        display: grid;
        gap: 0.75rem;
      }

      :where(.split-button-forms__actions) {
        display: flex;
        flex-wrap: wrap;
        gap: 0.75rem;
      }
    """


preview = SplitButtonForms()

preview  # noqa: B018

An open Menu closes internally before an uncontrolled primary default action. Its public onOpenChange action notice runs afterward, so the callback cannot cancel or duplicate the accepted native submit or reset. A valid form.requestSubmit(primary) follows the same rule. Native constraint validation can prevent submission before a submit event; that path leaves the Menu unchanged.

Without JavaScript, an enabled primary submit or reset remains a useful native Button, while server disabled or loading output uses CButton's native-safe fallback. The Menu Button cannot toggle before initialization. A closed Menu stays noninteractive in server flow, and an initially open Menu remains readable; neither path can submit the Form.

Control Menu visibility

Pass a Boolean client open to own Menu visibility. Omit it or pass null to release control from the latest committed state. onOpenChange reports Menu gestures and the primary action close request. Forced disabled or ancestor closes cannot be refused.

Control Split Button Menu visibility
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class ControlledSplitButtonMenu(Component):
    template = """
      <section
        class="split-button-controlled"
        x-data="{
          open:false,
          controlled:true,
          accept:true,
          lastReason:'none'
        }"
      >
        <c-CSplitButton
          label="Publication actions"
          menu_label="More publication actions"
          $c-props="{
            open: controlled ? open : null,
            onOpenChange: (nextOpen, detail) => {
              lastReason = detail.reason;
              if (controlled && accept) open = nextOpen;
            },
          }"
        >
          <c-fill name="default">Publish specimen</c-fill>
          <c-fill name="menu">
            <c-CMenuItem value="preview">Preview publication</c-CMenuItem>
            <c-CMenuItem value="schedule">Schedule publication</c-CMenuItem>
          </c-fill>
        </c-CSplitButton>

        <label>
          <input type="checkbox" x-model="accept" />
          Accept Menu requests
        </label>
        <div role="group" aria-label="Menu owner controls">
          <button type="button" @click="controlled=true;open=true">
            Show
          </button>
          <button type="button" @click="controlled=true;open=false">
            Hide
          </button>
          <button type="button" @click="controlled=false">
            Release control
          </button>
        </div>
        <output>
          Ownership:
          <span x-text="controlled ? 'controlled' : 'released'">
            controlled
          </span>
          · Last reason:
          <span x-text="lastReason">none</span>
        </output>
      </section>
    """

    css = """
      :where(.split-button-controlled) {
        display: grid;
        gap: 0.85rem;
        justify-items: start;
        min-block-size: 17rem;
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }

      :where(.split-button-controlled [role="group"]) {
        display: flex;
        flex-wrap: wrap;
        gap: 0.5rem;
      }
    """


preview = ControlledSplitButtonMenu()

preview  # noqa: B018

Primary native events remain distinct from Menu callbacks. Pass @click through primary_attrs, and use onAction for valued Menu commands and choices.

Choose presentation and placement

variant, intent, and size style both Buttons. block fills the available inline size while the Menu Button keeps its target width. placement and match_width use the full joined group as their anchor, not the narrow Menu Button.

Variants, sizes, and placement
Customize example
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SplitButtonVariantsAndSizes(Component):
    template = """
      <section
        class="split-button-variants"
        x-data="{
          variant:'solid',
          intent:'primary',
          size:'md',
          block:false,
          placement:'bottom-end',
          match_width:false
        }"
        @citry-ui-preview-controls.window="Object.assign($data, $event.detail)"
      >
        <div class="split-button-variants__subject">
          <c-CSplitButton
            label="Live collection actions"
            menu_label="More live collection actions"
            $c-props="{
              variant,
              intent,
              size,
              block,
              placement,
              matchWidth: match_width
            }"
          >
            <c-fill name="default">Collect specimen</c-fill>
            <c-fill name="menu">
              <c-CMenuItem value="photograph">Photograph first</c-CMenuItem>
              <c-CMenuItem value="label">Print field label</c-CMenuItem>
            </c-fill>
          </c-CSplitButton>
        </div>
        <div class="split-button-variants__matrix">
          <c-CSplitButton
            label="Approve actions"
            menu_label="More approve actions"
            variant="outline"
            intent="success"
            size="sm"
          >
            <c-fill name="default">Approve</c-fill>
            <c-fill name="menu">
              <c-CMenuItem value="review">Return to review</c-CMenuItem>
            </c-fill>
          </c-CSplitButton>
          <c-CSplitButton
            label="Warning actions"
            menu_label="More warning actions"
            variant="ghost"
            intent="warn"
            size="lg"
          >
            <c-fill name="default">Flag specimen</c-fill>
            <c-fill name="menu">
              <c-CMenuItem value="quarantine" intent="danger">
                Quarantine specimen
              </c-CMenuItem>
            </c-fill>
          </c-CSplitButton>
        </div>
      </section>
    """

    css = """
      :where(.split-button-variants) {
        display: grid;
        gap: 1.5rem;
        min-block-size: 20rem;
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }

      :where(.split-button-variants__subject) {
        inline-size: min(100%, 30rem);
      }

      :where(.split-button-variants__matrix) {
        display: flex;
        flex-wrap: wrap;
        gap: 1rem;
        align-items: start;
      }
    """


preview_controls = (
    {
        "name": "variant",
        "label": "Variant",
        "type": "select",
        "default": "solid",
        "options": (
            ("solid", "Solid"),
            ("outline", "Outline"),
            ("ghost", "Ghost"),
        ),
    },
    {
        "name": "intent",
        "label": "Intent",
        "type": "select",
        "default": "primary",
        "options": (
            ("primary", "Primary"),
            ("neutral", "Neutral"),
            ("success", "Success"),
            ("warn", "Warn"),
            ("danger", "Danger"),
        ),
    },
    {
        "name": "size",
        "label": "Size",
        "type": "select",
        "default": "md",
        "options": (("sm", "Small"), ("md", "Medium"), ("lg", "Large")),
    },
    {
        "name": "placement",
        "label": "Placement",
        "type": "select",
        "default": "bottom-end",
        "options": (
            ("bottom-start", "Bottom start"),
            ("bottom-end", "Bottom end"),
            ("top-start", "Top start"),
            ("top-end", "Top end"),
        ),
    },
    {"name": "block", "label": "Full width", "type": "checkbox", "default": False},
    {
        "name": "match_width",
        "label": "Match group width",
        "type": "checkbox",
        "default": False,
    },
)


preview = SplitButtonVariantsAndSizes()

preview  # noqa: B018

Keep alternatives available while loading

loading affects only the primary action. It remains focusable, exposes busy state, and blocks new activation while an otherwise enabled Menu remains available. Use common disabled when both halves must be unavailable, or the per-half inputs when only one action is unavailable.

Disabled and loading states
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SplitButtonDisabledAndLoading(Component):
    template = """
      <section
        class="split-button-disabled-demo"
        x-data="{
          disabled: false,
          primaryDisabled: false,
          menuDisabled: false,
          loading: false,
          saves: 0,
          last: 'Ready',
        }"
      >
        <h2>Large specimen image</h2>
        <div class="split-button-disabled-demo__controls" aria-label="Split Button state">
          <label><input type="checkbox" x-model="disabled" /> Disable both</label>
          <label><input type="checkbox" x-model="primaryDisabled" /> Disable primary</label>
          <label><input type="checkbox" x-model="menuDisabled" /> Disable Menu</label>
          <label><input type="checkbox" x-model="loading" /> Save pending</label>
        </div>

        <c-CSplitButton
          label="Specimen image actions"
          menu_label="More specimen image actions"
          c-primary_attrs="{'@click':'saves += 1; last = `Saved ${saves} times`'}"
          $c-props="{
            disabled,
            primaryDisabled,
            menuDisabled,
            loading,
            onAction: (value) => last = value,
          }"
        >
          <c-fill name="default">Save image</c-fill>
          <c-fill name="menu">
            <c-CMenuItem value="Export TIFF">Export TIFF</c-CMenuItem>
            <c-CMenuItem value="Export JPEG">Export JPEG</c-CMenuItem>
          </c-fill>
        </c-CSplitButton>
        <output aria-live="polite" x-text="last">Ready</output>

        <fieldset disabled>
          <legend>Disabled fieldset lifecycle</legend>
          <c-CSplitButton
            label="Fieldset-owned image actions"
            menu_label="More fieldset-owned image actions"
          >
            <c-fill name="default">Save fieldset image</c-fill>
            <c-fill name="menu">
              <c-CMenuItem value="export-fieldset">Export fieldset image</c-CMenuItem>
            </c-fill>
          </c-CSplitButton>
        </fieldset>
      </section>
    """

    css = """
      :where(.split-button-disabled-demo) {
        display: grid;
        gap: 1rem;
        justify-items: start;
        inline-size: min(100%, 34rem);
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }

      :where(.split-button-disabled-demo h2) { margin: 0; }
      :where(.split-button-disabled-demo__controls) {
        display: flex;
        flex-wrap: wrap;
        gap: 0.75rem 1rem;
      }
      :where(.split-button-disabled-demo__controls label) {
        display: inline-flex;
        gap: 0.4rem;
        align-items: center;
      }
      :where(.split-button-disabled-demo fieldset) {
        inline-size: 100%;
        padding: 1rem;
        border: 1px solid GrayText;
        border-radius: 0.75rem;
      }

      @media (forced-colors: active) {
        :where(.split-button-disabled-demo fieldset) { border-color: CanvasText; }
      }
    """


preview = SplitButtonDisabledAndLoading()

preview  # noqa: B018

Native disabled fieldset ancestry remains authoritative. When disabling the Menu hides focused Menu content, focus moves to an enabled primary Button or a safe modal/document fallback.

Reuse the complete Menu collection

The menu slot accepts the current CMenuItem, CMenuCheckboxItem, CMenuRadioGroup, CMenuRadioItem, CMenuGroup, CMenuSeparator, and CMenuSubmenu declarations. Their values, callbacks, paths, parts, keyboard rules, and content limits remain the CMenu contract.

Commands, choices, groups, and submenus
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SplitButtonMenuComposition(Component):
    template = """
      <section
        class="split-button-menu-composition"
        x-data="{publicRecord:true, format:'tiff', last:'No Menu action yet'}"
        dir="rtl"
      >
        <h2>Specimen publication</h2>
        <c-CSplitButton
          label="Specimen publication actions"
          menu_label="More specimen publication actions"
          c-close_on_select="False"
          $c-props="{onAction:(value, detail)=>last=`${detail.path.join(' / ') || 'root'}: ${value}`}"
        >
          <c-fill name="default">Publish specimen</c-fill>
          <c-fill name="menu">
            <c-CMenuItem value="copy-citation">Copy citation</c-CMenuItem>
            <c-CMenuItem href="#specimen-public-record">Open public record</c-CMenuItem>
            <c-CMenuCheckboxItem
              value="public-record"
              $c-props="{
                checked: publicRecord,
                onCheckedChange: (next) => publicRecord = next,
              }"
            >
              Publicly visible
            </c-CMenuCheckboxItem>
            <c-CMenuRadioGroup
              value="tiff"
              $c-props="{
                value: format,
                onValueChange: (next) => format = next,
              }"
            >
              <c-fill name="label">Export format</c-fill>
              <c-fill name="default">
                <c-CMenuRadioItem value="tiff">TIFF</c-CMenuRadioItem>
                <c-CMenuRadioItem value="jpeg">JPEG</c-CMenuRadioItem>
              </c-fill>
            </c-CMenuRadioGroup>
            <c-CMenuSeparator />
            <c-CMenuGroup>
              <c-fill name="label">Archive destination</c-fill>
              <c-fill name="default">
                <c-CMenuSubmenu value="regional-archive">
                  <c-fill name="label">Regional archive</c-fill>
                  <c-fill name="default">
                    <c-CMenuItem value="alpine">Alpine collection</c-CMenuItem>
                    <c-CMenuItem value="coastal">Coastal collection</c-CMenuItem>
                  </c-fill>
                </c-CMenuSubmenu>
              </c-fill>
            </c-CMenuGroup>
            <c-CMenuItem value="withdraw" intent="danger">Withdraw record</c-CMenuItem>
          </c-fill>
        </c-CSplitButton>
        <output aria-live="polite" x-text="last">No Menu action yet</output>
        <p id="specimen-public-record">The linked public record remains native navigation.</p>
      </section>
    """

    css = """
      :where(.split-button-menu-composition) {
        display: grid;
        gap: 0.875rem;
        justify-items: start;
        min-block-size: 23rem;
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }
      :where(.split-button-menu-composition h2, .split-button-menu-composition p) { margin: 0; }
      :where(.split-button-menu-composition output) {
        max-inline-size: 100%;
        overflow-wrap: anywhere;
      }
    """


preview = SplitButtonMenuComposition()

preview  # noqa: B018

Do not repeat the primary action in the Menu. Give the Menu Button a full secondary name such as “More save specimen actions”, not only “More”.

Use the two-stop keyboard model

Tab visits the primary Button and then the Menu Button in DOM order in both LTR and RTL. Enter and Space activate the focused native Button. Arrow Down or Arrow Up on the Menu Button opens and focuses the first or last item. The primary does not gain Menu arrow behavior.

Focus and keyboard behavior
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SplitButtonFocusAndKeyboard(Component):
    template = """
      <section
        class="split-button-keyboard-demo"
        x-data="{trace:[], loading:false, primaryDisabled:false, menuDisabled:false}"
      >
        <h2>Keyboard specimen workflow</h2>
        <p>
          Tab reaches the primary and Menu Button in DOM order. Enter or Space activates the
          focused Button. In the Menu, use arrows, Home, End, typeahead, and Escape.
        </p>
        <div class="split-button-keyboard-demo__controls">
          <label><input type="checkbox" x-model="loading" /> Primary loading</label>
          <label><input type="checkbox" x-model="primaryDisabled" /> Primary disabled</label>
          <label><input type="checkbox" x-model="menuDisabled" /> Menu disabled</label>
        </div>

        <div class="split-button-keyboard-demo__row" dir="ltr">
          <span>LTR</span>
          <c-CSplitButton
            label="Keyboard save actions"
            menu_label="More keyboard save actions"
            c-primary_attrs="{'@focus':'trace.push(`LTR primary`)'}"
            c-trigger_attrs="{'@focus':'trace.push(`LTR menu`)'}"
            $c-props="{loading, primaryDisabled, menuDisabled}"
          >
            <c-fill name="default">Save field note</c-fill>
            <c-fill name="menu">
              <c-CMenuItem value="duplicate">Duplicate note</c-CMenuItem>
              <c-CMenuItem value="export">Export note</c-CMenuItem>
              <c-CMenuItem value="archive">Archive note</c-CMenuItem>
            </c-fill>
          </c-CSplitButton>
        </div>

        <div class="split-button-keyboard-demo__row" dir="rtl">
          <span>RTL</span>
          <c-CSplitButton
            label="إجراءات حفظ العينة"
            menu_label="المزيد من إجراءات حفظ العينة"
            c-primary_attrs="{'@focus':'trace.push(`RTL primary`)'}"
            c-trigger_attrs="{'@focus':'trace.push(`RTL menu`)'}"
          >
            <c-fill name="default">حفظ ملاحظة العينة</c-fill>
            <c-fill name="menu">
              <c-CMenuItem value="duplicate-rtl">نسخ الملاحظة</c-CMenuItem>
              <c-CMenuItem value="export-rtl">تصدير الملاحظة</c-CMenuItem>
            </c-fill>
          </c-CSplitButton>
        </div>

        <output aria-live="polite" x-text="trace.length ? trace.join(' → ') : 'Focus trace is empty'">
          Focus trace is empty
        </output>
        <button type="button" @click="trace=[]">Clear focus trace</button>
      </section>
    """

    css = """
      :where(.split-button-keyboard-demo) {
        display: grid;
        gap: 1rem;
        inline-size: min(100%, 32rem);
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }
      :where(.split-button-keyboard-demo h2, .split-button-keyboard-demo p) { margin: 0; }
      :where(.split-button-keyboard-demo__controls) {
        display: flex;
        flex-wrap: wrap;
        gap: 0.75rem;
      }
      :where(.split-button-keyboard-demo__row) {
        display: grid;
        gap: 0.5rem;
        justify-items: start;
        inline-size: min(100%, 20rem);
      }
      :where(.split-button-keyboard-demo output) { overflow-wrap: anywhere; }
    """


preview = SplitButtonFocusAndKeyboard()

preview  # noqa: B018

Once open, the collection uses CMenu arrow navigation, Home, End, typeahead, submenus, Escape, and Tab behavior without trapping focus.

Compose with clipping and Dialogs

The Menu uses the native top layer and the shared anchored-layer coordinator. It escapes ordinary overflow while placement and width still follow the full SplitButton root. A sibling Dialog opened by either action owns modal focus.

Clipped layers, ShadowRoot, and sibling Dialog
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SplitButtonLayersAndDialog(Component):
    template = """
      <section
        class="split-button-layer-demo"
        x-data="{dialogOpen:false,last:'No layer action yet'}"
        @click="if ($event.target.closest('[data-open-provenance]')) dialogOpen=true"
        x-init="$nextTick(() => {
          const host = $refs.shadowHost;
          const fixture = $refs.shadowFixture;
          if (!host.shadowRoot && fixture) host.attachShadow({mode:'open'}).append(fixture);
        })"
      >
        <h2>Clipped specimen tray</h2>
        <div class="split-button-layer-demo__clip" dir="rtl">
          <c-CSplitButton
            label="Clipped specimen actions"
            menu_label="More clipped specimen actions"
            placement="bottom-end"
            c-primary_attrs="{'data-open-provenance':'','@click':'last=`Primary requested provenance`'}"
            $c-props="{onAction:(value)=>{
              last=value;
              if (value === 'open-provenance') dialogOpen=true;
            }}"
          >
            <c-fill name="default">Record provenance</c-fill>
            <c-fill name="menu">
              <c-CMenuItem value="open-provenance">Open provenance Dialog</c-CMenuItem>
              <c-CMenuSubmenu value="archive">
                <c-fill name="label">Archive destination</c-fill>
                <c-fill name="default">
                  <c-CMenuItem value="alpine-archive">Alpine archive</c-CMenuItem>
                  <c-CMenuItem value="coastal-archive">Coastal archive</c-CMenuItem>
                </c-fill>
              </c-CMenuSubmenu>
            </c-fill>
          </c-CSplitButton>
        </div>

        <div x-ref="shadowHost" class="split-button-layer-demo__shadow-host">
          <div x-ref="shadowFixture">
            <c-CSplitButton
              label="Shadow specimen actions"
              menu_label="More Shadow specimen actions"
            >
              <c-fill name="default">Save Shadow specimen</c-fill>
              <c-fill name="menu">
                <c-CMenuItem value="shadow-export">Export from ShadowRoot</c-CMenuItem>
              </c-fill>
            </c-CSplitButton>
          </div>
        </div>

        <output aria-live="polite" x-text="last">No layer action yet</output>
        <c-CDialog
          size="sm"
          $c-props="{
            open:dialogOpen,
            onOpenChange:(next)=>dialogOpen=next,
          }"
        >
          <c-fill name="title">Specimen provenance</c-fill>
          <c-fill name="default">Collected above the tree line during the August survey.</c-fill>
          <c-fill name="actions" data="{ close_attrs }">
            <c-CButton c-attrs="close_attrs">Close provenance</c-CButton>
          </c-fill>
        </c-CDialog>
      </section>
    """

    css = """
      :where(.split-button-layer-demo) {
        display: grid;
        gap: 1rem;
        justify-items: start;
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }
      :where(.split-button-layer-demo h2) { margin: 0; }
      :where(.split-button-layer-demo__clip) {
        overflow: hidden;
        inline-size: min(100%, 24rem);
        block-size: 7rem;
        padding: 2rem;
        border: 1px solid color-mix(in srgb, CanvasText 30%, transparent);
        border-radius: 0.75rem;
      }
      :where(.split-button-layer-demo__shadow-host) {
        display: block;
        padding: 0.75rem;
        border: 1px dashed GrayText;
      }
    """


preview = SplitButtonLayersAndDialog()

preview  # noqa: B018

Render Dialog and other peer overlays as siblings. Menu declaration content still follows CMenu's noninteractive item-content boundary.

Customize the joined control and Menu

Both Buttons consume the public --cui-button-* variables, and the Menu keeps the public --cui-menu-* contract. SplitButton adds variables for its divider, Menu Button width, and joined radius. Stable part selectors target each half and its content without changing semantic ownership.

Brand and environment customization
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SplitButtonCustomization(Component):
    template = """
      <section class="split-button-brand-demo">
        <article class="split-button-brand-demo__card split-button-brand-demo__card--orchard">
          <p>Orchard field guide</p>
          <c-CSplitButton
            class_="split-button-brand-demo__subject"
            label="Orchard specimen publishing actions"
            menu_label="More Orchard specimen publishing actions"
            open
          >
            <c-fill name="default">Publish alpine gentian observation</c-fill>
            <c-fill name="menu">
              <c-CMenuItem value="orchard-draft">Save Orchard draft</c-CMenuItem>
              <c-CMenuItem value="orchard-export">Export Orchard record</c-CMenuItem>
            </c-fill>
          </c-CSplitButton>
        </article>

        <article
          class="split-button-brand-demo__card split-button-brand-demo__card--harbor"
          dir="rtl"
        >
          <p>دليل ميناء للأبحاث الميدانية</p>
          <c-CSplitButton
            class_="split-button-brand-demo__subject"
            label="إجراءات نشر ملاحظة العينة الساحلية"
            menu_label="المزيد من إجراءات نشر ملاحظة العينة الساحلية"
            variant="outline"
            open
          >
            <c-fill name="default">نشر ملاحظة العينة الساحلية الطويلة</c-fill>
            <c-fill name="menu">
              <c-CMenuItem value="harbor-draft">حفظ المسودة الساحلية</c-CMenuItem>
              <c-CMenuItem value="harbor-export">تصدير السجل الساحلي</c-CMenuItem>
            </c-fill>
          </c-CSplitButton>
        </article>
      </section>
    """

    css = """
      :where(.split-button-brand-demo) {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
        gap: 1rem;
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }
      :where(.split-button-brand-demo__card) {
        min-block-size: 18rem;
        padding: 1.25rem;
        border-radius: 1rem;
      }
      :where(.split-button-brand-demo__card > p) { margin-block: 0 1rem; font-weight: 700; }
      :where(.split-button-brand-demo__subject) { max-inline-size: 20rem; }

      :where(.split-button-brand-demo__card--orchard) {
        color-scheme: light;
        background: #f5f0df;
        --cui-button-background: #315f37;
        --cui-button-foreground: #fffdf5;
        --cui-button-hover-background: #244c2a;
        --cui-menu-background: #fffdf5;
        --cui-menu-foreground: #203422;
        --cui-menu-focus-background: #d9e9cf;
        --cui-menu-focus-foreground: #17351c;
        --cui-split-button-divider-color: #c5d7bb;
        --cui-split-button-divider-width: 2px;
        --cui-split-button-radius: 0.75rem;
      }

      :where(.split-button-brand-demo__card--harbor) {
        color-scheme: dark;
        background: #102b38;
        --cui-button-background: #c6ecff;
        --cui-button-foreground: #082633;
        --cui-button-border-color: #79bfdc;
        --cui-button-hover-background: #a7ddf5;
        --cui-menu-background: #173c4c;
        --cui-menu-foreground: #eefaff;
        --cui-menu-focus-background: #95d9f4;
        --cui-menu-focus-foreground: #062531;
        --cui-split-button-divider-color: #29586b;
        --cui-split-button-divider-width: 1px;
        --cui-split-button-radius: 0.375rem;
      }

      :where(.split-button-brand-demo [data-citry-ui-part="split-button-primary"]) {
        min-inline-size: 0;
      }
      :where(.split-button-brand-demo [data-citry-ui-part="split-button-primary-content"]) {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }

      @media (prefers-reduced-motion: reduce) {
        :where(.split-button-brand-demo) {
          --cui-menu-duration: 0ms;
        }
      }
      @media (forced-colors: active) {
        :where(.split-button-brand-demo__card) {
          border: 1px solid CanvasText;
          forced-color-adjust: auto;
        }
      }
      @media print {
        :where(.split-button-brand-demo__card) {
          break-inside: avoid;
          border: 1px solid currentColor;
          background: transparent;
          color: black;
        }
      }
    """


preview = SplitButtonCustomization()

preview  # noqa: B018

The horizontal compound keeps the primary at logical start in RTL, preserves the Menu Button target width at narrow sizes, removes motion under reduced motion, and retains visible focus and divider boundaries in forced colors.

Choose explicit composition for a different policy

Compose CButtonGroup and CMenu when the primary must be a link, actions are equal peers, the layout must be vertical, or the two surfaces need separate state owners. SplitButton intentionally keeps one dominant command, one Menu owner, one horizontal anatomy, and no imperative methods or custom DOM events.

Trust the four attribute destinations deliberately

attrs, primary_attrs, trigger_attrs, and menu_attrs are copied and validated for their documented roots. They accept ordinary styling, language, permitted ARIA, and data-* except data-citry-*, data-cev*, data-cid*, and owned reflections. @event and x-on:event Alpine listeners are allowed; raw on* browser-expression attributes are rejected. The primary also accepts the documented native Form attributes, but URL-like action destinations remain consumer-owned and are not sanitized or trusted by Citry. Component-owned identity, semantics, state, focus order, popover targeting, Citry runtime fields, and structural Alpine ownership are rejected.

Primary content accepts text and decorative noninteractive content. The final Button needs a nonempty accessible name from visible text, aria-label, or aria-labelledby. Menu declarations retain CMenu's exact trust boundary.

API reference

Inputs

CSplitButton server inputs

Server inputs are passed in a template through <c-CSplitButton ... /> or in Python through CSplitButton(...).

InputTypeDefaultEffect
idstr | NonegeneratedSets the literal root ID base and the primary, Menu Button, and Menu surface ID family.
labelnon-whitespace strrequiredNames the related two-Button group.
menu_labelnon-whitespace strrequiredNames the secondary Menu Button with its specific purpose.
type"button" | "submit" | "reset" (CButtonType)"button"Selects the primary native Button activation and Form behavior.
disabledboolFalseDisables both Buttons and force-closes the Menu.
primary_disabledboolFalseDisables only the primary Button.
menu_disabledboolFalseDisables only the Menu Button and force-closes the Menu.
loadingboolFalseMarks only the primary pending, retains its focus, and blocks new primary activation.
variant"solid" | "outline" | "ghost" (CButtonVariant)"solid"Sets both Buttons' presentation strength.
intent"primary" | "neutral" | "success" | "warn" | "danger" (CButtonIntent)"primary"Sets both Buttons' semantic color role.
size"sm" | "md" | "lg" (CButtonSize)"md"Sets both Buttons and Menu item geometry.
blockboolFalseFills the containing inline size while the primary takes remaining space.
loading_pos"start" | "center" | "end" (CButtonLoadingPos)"center"Places the primary loading indicator.
openboolFalseSets initial Menu visibility and the uncontrolled fallback.
loopboolTrueWraps Menu arrow navigation and typeahead.
placement"top-start" | "top" | "top-end" | "bottom-start" | "bottom" | "bottom-end" (CMenuPlacement)"bottom-end"Sets the preferred logical placement relative to the full group.
match_widthboolFalseMatches the full group width up to the Menu viewport-safe maximum.
close_on_selectboolTrueSets the root Menu default close policy.
class_str | Mapping[str, bool] | Sequence[CClassValue] | None (CClassValue)NoneAdds root classes and merges them with attrs.
stylestr | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None (CStyleValue)NoneAdds root styles; generated anchor ownership merges last.
attrsMapping[str, object] | NoneNoneAdds copied allowed attributes to the labelled group root.
primary_attrsMapping[str, object] | NoneNoneAdds copied allowed ARIA, Form, data, style, and native listener attributes to the primary Button.
trigger_attrsMapping[str, object] | NoneNoneAdds copied allowed descriptive, data, style, and native listener attributes to the Menu Button.
menu_attrsMapping[str, object] | NoneNoneAdds copied allowed descriptive, language, data, style, and native listener attributes to the root Menu surface.

CSplitButton client inputs

Client inputs are passed in the browser through the $c-props="{ ... }" attribute on <c-CSplitButton />.

InputTypeOmitted behaviorEffect
openboolean | nullReleases control from the latest committed state. null has the same effect.Controls Menu visibility while supplied as a Boolean.
disabledbooleanUses the server input.Controls common disabledness; native fieldset disabledness remains authoritative.
primaryDisabledbooleanUses the server input.Controls primary-only disabledness.
menuDisabledbooleanUses the server input.Controls Menu-only disabledness and forced close.
loadingbooleanUses the server input.Controls the focus-retaining primary pending guard.
variant"solid" | "outline" | "ghost" (CButtonVariant)Uses the server input.Controls both Buttons' presentation strength.
intent"primary" | "neutral" | "success" | "warn" | "danger" (CButtonIntent)Uses the server input.Controls both Buttons' semantic color role.
size"sm" | "md" | "lg" (CButtonSize)Uses the server input.Controls Button and Menu geometry.
blockbooleanUses the server input.Controls full-inline group layout.
loadingPosition"start" | "center" | "end" (CButtonLoadingPos)Uses the server input.Controls primary loading-indicator placement.
loopbooleanUses the server input.Controls Menu navigation wrapping.
placementsix logical placement strings (CMenuPlacement)Uses the server input.Controls requested full-root Menu placement.
matchWidthbooleanUses the server input.Controls clamped full-group width matching.
closeOnSelectbooleanUses the server input.Controls the root Menu default close policy.
onOpenChangefunctionOmission or null selects no visibility callback.Receives Menu requests, forced closes, and deferred primary action-close notices.
onActionfunctionOmission or null selects no Menu action callback.Receives valued Menu command and choice activations.

Slots

Slots are passed as nested content or <c-fill> tags in a template, or through the slots={...} argument in Python.

CSplitButton slots

SlotRequiredDataFallback
defaultyes{} (CSplitButtonDefaultSlotData)None. Must be structurally nonempty and help provide the final primary accessible name.
startno{} (CSplitButtonStartSlotData)Omitted.
endno{} (CSplitButtonEndSlotData)Omitted.
loadingno{} (CSplitButtonLoadingSlotData)CSS spinner hidden from accessibility.
menuyes{} (CSplitButtonMenuSlotData)None. Requires a nonempty collection of existing CMenu declarations.

Events

Component events are callback inputs supplied through $c-props. Native browser events remain available through Alpine @... attributes.

CSplitButton events

EventSignatureTrigger and timingDetailControlled and cancellation behavior
onOpenChange(requestedOpen: boolean, detail: CMenuOpenChangeDetail) => void (CMenuOpenChangeDetail)A root Menu visibility request, forced close, or accepted primary action close occurs.{reason, controlled, forced, source} (CMenuOpenChangeDetail)Menu gestures use CMenu timing. A primary action notice runs in a cancelable zero-delay task after native activation; uncontrolled state already closed and controlled state waits.
onAction(value: string, detail: CMenuActionDetail) => void (CMenuActionDetail)An enabled valued Menu command, checkbox, or radio activates.{kind, item, event, path} (CMenuActionDetail)Uses CMenu callback order and fires once. The primary action, links, and anonymous commands do not fire it.

Methods

-

CSS

CSS variables to theme the components. Set them on an ancestor or the component itself.

CSplitButton CSS variables

Apply these variables to CSplitButton or one of its ancestors.

VariableTypePurposeDefault
--cui-split-button-divider-colorcolorBoundary between the two native Buttons.color-mix(in srgb, currentColor 32%, transparent)
--cui-split-button-divider-widthlengthJoined divider width and overlap.1px
--cui-split-button-menu-inline-sizelengthMenu Button inline target size.Effective Button height.
--cui-split-button-radiuslengthJoined outer corners.var(--cui-button-radius, 0.5rem)
--cui-button-backgroundcolorBoth Button resting surfaces.Variant- and intent-derived color.
--cui-button-foregroundcolorBoth Button foregrounds.Derived contrast color.
--cui-button-border-colorcolorBoth Button borders.Variant- and intent-derived color.
--cui-button-hover-backgroundcolorEnabled hover surfaces.Derived color mix.
--cui-button-active-backgroundcolorEnabled active surfaces.Derived stronger color mix.
--cui-button-focus-colorcolorBoth focus-visible outlines.Highlight
--cui-button-radiuslengthSource Button radius used by the joined fallback.0.5rem
--cui-button-font-weightfont-weightBoth Button labels.600
--cui-button-gaplengthPrimary content-region gaps.0.5rem
--cui-button-disabled-opacitynumberDisabled Button content opacity.0.48
--cui-button-heightlengthBoth Button minimum block size.Size-derived length.
--cui-button-inline-paddinglengthPrimary inline padding.Size-derived length.
--cui-button-block-paddinglengthBoth Button block padding.Size-derived length.
--cui-button-font-sizelengthBoth Button font size.Size-derived length.
--cui-menu-backgroundcolorRoot and submenu surfaces.Canvas
--cui-menu-foregroundcolorMenu item text.CanvasText
--cui-menu-muted-colorcolorMenu descriptions, labels, and shortcuts.color-mix(in srgb, current foreground 72%, transparent)
--cui-menu-border-colorcolorMenu surface and separator boundaries.color-mix(in srgb, CanvasText 18%, transparent)
--cui-menu-border-widthlengthMenu surface boundary width.1px
--cui-menu-radiuslengthMenu surface corners.0.75rem
--cui-menu-shadowshadowRoot Menu elevation.0 0.75rem 2rem rgb(15 23 42 / 18%)
--cui-menu-submenu-shadowshadowNested Menu elevation.0 1rem 2.5rem rgb(15 23 42 / 22%)
--cui-menu-inline-sizelengthPreferred Menu width.14rem
--cui-menu-min-inline-sizelengthMinimum useful submenu corridor.10rem
--cui-menu-max-inline-sizelengthViewport-safe Menu width.calc(100dvi - 1rem)
--cui-menu-max-block-sizelengthMenu scroll limit.min(24rem, calc(100dvb - 1rem))
--cui-menu-paddinglengthMenu surface edge spacing.0.375rem
--cui-menu-item-block-sizelengthMenu item minimum height.Size-derived.
--cui-menu-item-padding-inlinelengthMenu item inline spacing.Size-derived.
--cui-menu-item-gaplengthMenu item-region gap.0.625rem
--cui-menu-item-radiuslengthMenu item corners.0.5rem
--cui-menu-hover-backgroundcolorMenu pointer-hover fill.color-mix(in srgb, CanvasText 8%, transparent)
--cui-menu-focus-backgroundcolorFocused Menu item fill.light-dark(#175cd3, #84adff)
--cui-menu-focus-foregroundcolorFocused Menu item content.light-dark(#ffffff, #101828)
--cui-menu-focus-outline-colorcolorMenu item focus-visible outline.light-dark(#175cd3, #84adff)
--cui-menu-danger-colorcolorDestructive Menu item content.light-dark(#b42318, #fda29b)
--cui-menu-disabled-opacitynumberDisabled Menu content opacity.0.5
--cui-menu-offsetlengthRoot Menu anchor gap.0.375rem
--cui-menu-submenu-offsetlengthNested Menu anchor gap.0.25rem
--cui-menu-durationtimeMenu entry and exit duration.120ms
--cui-menu-easingeasingMenu entry and exit curve.cubic-bezier(0.2, 0.8, 0.2, 1)

Attributes

HTML attributes defined on the components that you can refer to for CSS, inspection, and testing. Read-only.

CSplitButton attributes

AttributeElementTypeMeaning
roleRoot"group"Groups the dominant action and its related Menu Button.
aria-labelRootnon-whitespace stringUses the required group label.
data-variantRoot"solid" | "outline" | "ghost" (CButtonVariant)Mirrors effective common presentation.
data-intentRootfive CButton intents (CButtonIntent)Mirrors effective common semantic color.
data-sizeRoot"sm" | "md" | "lg" (CButtonSize)Mirrors effective Button and Menu geometry.
data-blockRootpresent | absentPresent when the group fills available inline size.
data-disabledRootpresent | absentMirrors the common disabled override.
data-primary-disabledRootpresent | absentMirrors browser-effective primary disabledness.
data-menu-disabledRootpresent | absentMirrors browser-effective Menu Button disabledness.
data-loadingRootpresent | absentMirrors effective primary pending state.
data-loading-positionRoot"start" | "center" | "end" (CButtonLoadingPos)Mirrors primary loading placement.
data-openRootpresent | absentMirrors committed root Menu visibility.

CSplitButton attributes

AttributeElementTypeMeaning
idPrimary Buttonroot ID plus -primaryUses the exact owned primary identity.
typePrimary Button"button" | "submit" | "reset" (CButtonType)Preserves the authored native action.
disabledPrimary Buttonpresent | absentRepresents effective native disabledness and the no-JavaScript loading fallback.
aria-busyPrimary Button"true" | absentPresent only while primary work is pending.
aria-disabledPrimary Button"true" | absentPresent while disabled or loading.
data-disabledPrimary Buttonpresent | absentMirrors effective disabledness.
data-loadingPrimary Buttonpresent | absentMirrors effective pending state.
data-variantPrimary Buttonthree CButton variants (CButtonVariant)Mirrors presentation strength.
data-intentPrimary Buttonfive CButton intents (CButtonIntent)Mirrors semantic color.
data-sizePrimary Buttonthree CButton sizes (CButtonSize)Mirrors geometry.
data-loading-positionPrimary Buttonthree positions (CButtonLoadingPos)Mirrors pending-indicator placement.

CSplitButton attributes

AttributeElementTypeMeaning
idMenu Buttonroot ID plus -menu-triggerUses the exact owned Menu Button identity.
typeMenu Button"button"Prevents Form submission in every state.
aria-labelMenu Buttonnon-whitespace stringUses menu_label as the explicit secondary action name.
aria-haspopupMenu Button"menu"Announces the popup kind.
aria-controlsMenu ButtonMenu surface IDREFReferences the owned root Menu surface.
aria-expandedMenu Button"true" | "false"Mirrors logical root Menu state.
disabledMenu Buttonpresent | absentMirrors effective native Menu disabledness.
data-disabledMenu Buttonpresent | absentStyles effective Menu disabledness.
data-variantMenu Buttonthree CButton variants (CButtonVariant)Mirrors presentation strength.
data-intentMenu Buttonfive CButton intents (CButtonIntent)Mirrors semantic color.
data-sizeMenu Buttonthree CButton sizes (CButtonSize)Mirrors geometry.
idRoot Menu surfaceroot ID plus -menuUses the exact owned Menu surface identity.
popoverRoot and submenu Menu surfaces"manual"Uses native top-layer presence with Citry dismissal.
roleRoot and submenu Menu surfaces"menu"Exposes application Menu semantics.
aria-labelledbyRoot Menu surfaceMenu Button IDREFNames the root Menu from its trigger.
data-openRoot and submenu Menu surfacespresent | absentMirrors logical Menu visibility.
data-placementRoot Menu surfacesix logical placement strings (CMenuPlacement)Mirrors the requested root placement.
data-match-widthRoot Menu surfacepresent | absentIndicates clamped full-root width matching.
data-sizeRoot Menu surfacethree sizes (CButtonSize)Mirrors effective Menu item geometry.

CSplitButton attributes

AttributeElementTypeMeaning
aria-describedbyMenu item rootdescription IDREF | absentUses CMenu's optional separate item description.
aria-checkedCheckbox or radio item Button"false" | "true" | "mixed"Uses the effective CMenu choice state; radio items never use mixed.
data-checkedCheckbox or radio item Button"false" | "true" | "mixed"Mirrors the effective CMenu choice state for styling.

Selectors

Selectors for the DOM nodes in the components that you can use for CSS, inspection, and testing.

CSplitButton selectors

SelectorElementPurpose
[data-citry-ui-part="split-button"]Root divLabelled group and class, style, and attrs destination.
[data-citry-ui-part="split-button-primary"]Primary native ButtonDominant action and primary_attrs destination.
[data-citry-ui-part="split-button-primary-start"]Optional decorative wrapperPrimary logical-start content.
[data-citry-ui-part="split-button-primary-content"]Required content wrapperVisible dominant action content.
[data-citry-ui-part="split-button-primary-end"]Optional decorative wrapperPrimary logical-end content.
[data-citry-ui-part="split-button-primary-loading-indicator"]Stable decorative wrapperPrimary pending indicator.
[data-citry-ui-part="split-button-menu-trigger"]Secondary native ButtonMenu activation and trigger_attrs destination.
[data-citry-ui-part="split-button-menu-indicator"]Decorative spanLogical-down Menu indicator.
[data-citry-ui-part="menu"]Root or submenu Menu surfacePopover presence and collection focus.
[data-citry-ui-part="menu-item"]Command, link, checkbox, or radio rootMenu item styling.
[data-citry-ui-part="menu-item-start"]Decorative item wrapperItem logical-start content.
[data-citry-ui-part="menu-item-label"]Visible item labelLayout and exact owned label target.
[data-citry-ui-part="menu-item-description"]Optional descriptionSupporting text and accessible description.
[data-citry-ui-part="menu-item-end"]Decorative item wrapperShortcut or logical-end content.
[data-citry-ui-part="menu-choice-indicator"]Decorative choice markerCheckbox and radio state.
[data-citry-ui-part="menu-group"]Labelled group rootGeneric command grouping.
[data-citry-ui-part="menu-group-label"]Visible group labelExact group name and layout.
[data-citry-ui-part="menu-radio-group"]Radio group rootExclusive choice grouping.
[data-citry-ui-part="menu-separator"]Horizontal separatorCollection division.
[data-citry-ui-part="menu-submenu"]Neutral submenu wrapperChild trigger and surface ownership.
[data-citry-ui-part="menu-submenu-trigger"]Nested Menu ButtonSubmenu activation and placement anchor.

Interfaces

Aliases and data shapes referenced above.

Input type aliases

InterfaceDefinition
CClassValuestr | Mapping[str, bool] | Sequence[CClassValue]
CStyleValuestr | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]
CButtonTypeLiteral["button", "submit", "reset"]
CButtonVariantLiteral["solid", "outline", "ghost"]
CButtonIntentLiteral["primary", "neutral", "success", "warn", "danger"]
CButtonSizeLiteral["sm", "md", "lg"]
CButtonLoadingPosLiteral["start", "center", "end"]
CMenuPlacementLiteral["top-start", "top", "top-end", "bottom-start", "bottom", "bottom-end"]

CSplitButtonDefaultSlotData

Empty dataclass: {}.

CSplitButtonStartSlotData

Empty dataclass: {}.

CSplitButtonEndSlotData

Empty dataclass: {}.

CSplitButtonLoadingSlotData

Empty dataclass: {}.

CSplitButtonMenuSlotData

Empty dataclass: {}.

CMenuOpenChangeDetail

FieldTypeDefaultMeaning
reason"trigger" | "escape" | "outside" | "focus-outside" | "tab" | "action" | "native" | "disabled" | "ancestor"-Cause of the requested or forced visibility change.
controlledboolean-Whether a valid client Boolean owns desired Menu state.
forcedboolean-Whether native or structural safety overrides owner refusal.
sourceElement | EventTarget | null-Browser source associated with the change.

CMenuActionDetail

FieldTypeDefaultMeaning
kind"command" | "checkbox" | "radio"-Activated semantic Menu item kind.
itemElement-Activated Menu item root.
eventEvent-Native Menu activation event.
pathlist[str]-Canonical ancestor-submenu path from the SplitButton root Menu.

Translation keys

-