Skip to main content
Ctrl+K
bootstack is in pre-release — the API may still change before 1.0. Install with pip install --pre bootstack.
bootstack - Home bootstack - Home
  • User Guide
  • Widgets
  • API Reference
  • Production
  • GitHub
  • User Guide
  • Widgets
  • API Reference
  • Production
  • GitHub

Application

  • App
  • AppShell
  • Window

Actions

  • Button
  • ButtonGroup

Inputs

  • TextField
  • PasswordField
  • NumberField
  • Slider
  • RangeSlider
  • PathField
  • SpinnerField
  • TextArea
  • CodeEditor
  • DateField
  • TimeField

Selection

  • Checkbox
  • Select
  • Switch
  • ToggleButton
  • RadioGroup
  • ToggleGroup
  • SelectButton
  • Calendar

Data Display

  • Label
  • Badge
  • ProgressBar
  • Gauge
  • ListView
  • DataTable
  • Tree

Media

  • Picture
  • Gallery
  • Carousel
  • Avatar

Layout

  • Separator
  • Card
  • GroupBox
  • VStack
  • HStack
  • Grid
  • Accordion
  • ScrollView
  • SplitView

Chrome bars and menus

  • Menubar
  • CommandBar
  • StatusBar
  • MenuButton
  • ContextMenu

Navigation

  • PageStack
  • Tabs

Overlays

  • Tooltip
  • Toasts & Notifications

Dialogs

  • Message Dialogs
  • Input Dialogs
  • Color Dialog
  • Font Dialog
  • Filter Dialog
  • Dialog
  • Form Dialog

Forms

  • Form
  • Widgets
  • Carousel

Carousel#

Shows one image slide at a time, with prev/next navigation. Carousel is the focus-on-one counterpart to Gallery — it steps through a collection of image records one slide at a time, with overlaid chevrons, a slide indicator, an optional caption, transitions, and autoplay.

It pairs naturally with Gallery.on_item_activate to open a full-size pop-up viewer: a Carousel over the same items, starting at the activated slide.

Usage#

Slides#

Each record names the image through image_field (an Image, a file path, or a PIL image) and, optionally, a caption through caption_field:

items = [
    {"id": 1, "image": "beach.jpg",  "name": "Beach"},
    {"id": 2, "image": "sunset.jpg", "name": "Sunset"},
]

bs.Carousel(items=items, caption_field="name")

fit controls how each image scales into the stage — the same modes as Picture, defaulting to 'contain' so the whole image shows. corner_radius rounds the slide.

Navigating#

Users navigate with the overlaid chevrons, the arrow keys, or the indicator dots. Drive it from code with next, previous, and go_to, and read the state through index, current, and count:

carousel = bs.Carousel(items=items)
carousel.next()
carousel.go_to(3)
print(carousel.current)    # the current record dict

index is a live property — assign to it to jump to a slide.

Transitions#

transition animates the change between slides — 'slide' (default), 'fade', or 'none':

bs.Carousel(items=items, transition="slide")   # default
bs.Carousel(items=items, transition="fade")

Indicator and arrows#

indicator selects the slide indicator — 'dots' (default), 'count', or 'none'. Set show_arrows=False to hide the chevrons:

bs.Carousel(items=items, indicator="count")
bs.Carousel(items=items, show_arrows=False, indicator="dots")

With more than eight slides, 'dots' automatically switches to a current / total counter — a long row of dots is neither readable nor clickable:

Carousel with many slides — count indicator, light theme Carousel with many slides — count indicator, dark theme

Autoplay#

autoplay=True advances the slides on a timer; interval sets the dwell time per slide and loop wraps past the ends. Control playback with play and pause:

carousel = bs.Carousel(items=items, autoplay=True, interval=3000)
carousel.pause()
carousel.play()

Reacting to changes#

on_change fires when the active slide changes; on_item_click fires when the current slide is clicked — the hook for closing a pop-up viewer or opening a detail view:

carousel.on_change(lambda r: print("now showing", r["name"]))
carousel.on_item_click(lambda r: open_detail(r))

Full-size viewer from a Gallery#

A common pattern is to let a thumbnail Gallery open a larger view: double-clicking a thumbnail pops up a window with a full-size carousel of the same photos, starting on the one you picked. There is no special widget for this — you compose a Gallery and a Carousel. Because both read the same records, the only work is finding the activated record’s slide position:

photos = [
    {"id": 1, "image": "beach.jpg",  "name": "Beach"},
    {"id": 2, "image": "sunset.jpg", "name": "Sunset"},
    # ...
]

def open_viewer(record):
    # the record's id is its identity, not its position — find the index
    start = next(i for i, p in enumerate(photos) if p["id"] == record["id"])
    with bs.Window(title=record["name"], size=(900, 650),
                   modal=True, center_on_parent=True) as win:
        viewer = bs.Carousel(items=photos, index=start, fill="both", expand=True)
        viewer.on_item_click(lambda r: win.close())   # click the photo to close
    win.show()

gallery = bs.Gallery(items=photos, selection_mode="single")
gallery.on_item_activate(open_viewer)

Widget sizing#

All widgets accept self-placement kwargs via **kwargs. The parent container determines which options apply — stack-based parents use stack kwargs, grid-based parents use grid kwargs. Unrecognised keys are silently ignored.

Stack#

Used inside VStack, HStack, App, and other stack containers.

fill

Fill direction: 'x', 'y', 'both', or 'none'.

expand

Grow to consume extra space in the parent. True or False.

anchor

Alignment when the widget does not fill the available slot: 'n', 's', 'e', 'w', 'center', 'nw', etc.

margin

External spacing in pixels. Accepts an integer (equal on all sides), a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom).

margin_x

Horizontal external spacing (left and right). Accepts an integer or a 2-tuple (left, right) for asymmetric spacing. Overrides the horizontal component of margin=.

margin_y

Vertical external spacing (top and bottom). Accepts an integer or a 2-tuple (top, bottom) for asymmetric spacing. Overrides the vertical component of margin=.

Grid#

Used inside a Grid container.

row / column

Zero-based row and column indices.

rowspan / columnspan

Number of rows or columns to span.

sticky

Alignment and fill within the grid cell. Any combination of 'n', 's', 'e', 'w' — e.g. 'ew' stretches horizontally, 'nsew' fills the entire cell.

margin

External spacing in pixels. Accepts an integer, a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom).

margin_x

Horizontal external spacing. Accepts an integer or (left, right).

margin_y

Vertical external spacing. Accepts an integer or (top, bottom).

See also#

  • Gallery — the grid of thumbnails a carousel pairs with.

  • Picture — the single-image display widget.

  • Image — the image source handle.

API#

The complete reference for Carousel lives on the Widgets API page. At a glance:

Carousel

Shows one image slide at a time, with prev/next navigation.

Full Example#

 1
 2_DIR = tempfile.gettempdir()
 3_PALETTE = [
 4    (220, 90, 90), (90, 170, 110), (90, 130, 220), (210, 160, 70), (160, 110, 200),
 5]
 6
 7
 8def _slide(i: int) -> str:
 9    path = os.path.join(_DIR, f"bs_carousel_{i}.png")
10    w, h = 480, 300
11    base = _PALETTE[i % len(_PALETTE)]
12    img = PILImage.new("RGB", (w, h))
13    px = img.load()
14    for y in range(h):
15        for x in range(w):
16            px[x, y] = (
17                int(base[0] * (0.5 + 0.5 * x / w)),
18                int(base[1] * (0.5 + 0.5 * y / h)),
19                int(base[2] * (0.6 + 0.4 * y / h)),
20            )
21    ss = 4
22    layer = PILImage.new("RGBA", (w * ss, h * ss), (0, 0, 0, 0))
23    r = 70 * ss
24    cx, cy = w * ss // 2, h * ss // 2
25    ImageDraw.Draw(layer).ellipse([cx - r, cy - r, cx + r, cy + r], fill=(255, 255, 255, 225))
26    layer = layer.resize((w, h), Resampling.LANCZOS)
27    img = img.convert("RGBA")
28    img.alpha_composite(layer)
29    img.convert("RGB").save(path)
30    return path
31
32
33items = [{"id": i, "image": _slide(i), "name": f"Slide {i + 1}"} for i in range(5)]
34
35with bs.App(title="Carousel", size=(620, 460), padding=16, gap=8) as app:
36    bs.Label("Image carousel — chevrons, arrow keys, or dots", font="heading-md")
37    carousel = bs.Carousel(
38        items=items,
39        image_field="image",
40        caption_field="name",
41        fit="cover",
42        transition="slide",
43        indicator="dots",
44        corner_radius=14,
45        fill="both",
46        expand=True,
47    )
48    carousel.on_change(lambda r: print("slide:", r["name"]))
49    carousel.on_item_click(lambda r: print("clicked:", r["name"]))
50
51app.run()

previous

Gallery

next

Avatar

On this page
  • Usage
    • Slides
    • Navigating
    • Transitions
    • Indicator and arrows
    • Autoplay
    • Reacting to changes
    • Full-size viewer from a Gallery
    • Widget sizing
      • Stack
      • Grid
  • See also
  • API
  • Full Example

© Copyright 2026, Israel Dryer.

Created using Sphinx 9.1.0.

Built with the PyData Sphinx Theme 0.19.0.