Compose Unstyled

repository·main·Indexed 22 days ago

https://github.com/composablehorizons/compose-unstyled

A library built on Compose Foundation providing renderless, accessible UI components and a flexible theming system for cross-platform design systems. It includes headless components such as UnstyledCheckbox, UnstyledTabGroup, Bottom Sheets, Dropdown Menus, and Animated Dialogs, as well as tools for defining responsive window width and height breakpoints.

Tokens
41.9K
Snippets
207
Records
229
Agent score
77%

What's inside compose-unstyled

  1. Overview of Compose Unstyled

    main
    Compose Unstyled is a library built on top of Compose Foundation. It provides fully-renderless and fully accessible components, such as Bottom Sheets, Dropdown Menus, and Animated Dialogs. It includes a flexible theming system designed to allow developers to build custom design systems across any platform supported by Compose, extending beyond just Android.
  2. What is Compose Unstyled?

    main

    Compose Unstyled is a library of unstyled, accessible building blocks for Jetpack Compose and Compose Multiplatform. Unlike Material Compose, which enforces Google's Material Design, Compose Unstyled provides components with zero default styling. This allows developers to build custom design systems without having to implement complex UX details, accessibility semantics, or keyboard navigation from scratch.

    Key characteristics:

    • Zero Styling: Components render nothing visually by design; you provide all the styling.
    • Themable: You can create themes using your own design system's tokens.
    • Accessible: Components support keyboard navigation and follow ARIA spec semantics (tested on Android TalkBack).
    • Multiplatform: The public API is platform-agnostic, ensuring consistent behavior across desktop, web, and mobile.
  3. How UnstyledTextField and TextInput work together

    main

    The text field is composed of two main parts:

    1. UnstyledTextField: The container that manages the state, configuration (like line limits, keyboard options, and styling), and accessibility.
    2. TextInput: The component placed inside the UnstyledTextField content slot that actually renders the editable text and optional placeholder.

    To use them, pass a TextFieldState to the container and call TextInput() within its trailing lambda.

    UnstyledTextField(state = state) {
      TextInput()
    }
  4. How Toggle Switch and SwitchThumb work together

    main

    The Toggle Switch component is composed of two main parts:

    1. UnstyledSwitch: Represents the interactive switch container. It manages the checked state and interaction logic.
    2. SwitchThumb: A slot used to place content (the 'thumb') at the start or end of the switch layout.

    To build a switch, you wrap SwitchThumb inside the UnstyledSwitch content lambda.

    UnstyledSwitch(
      checked = checked,
      onCheckedChange = onCheckedChange,
    ) {
      SwitchThumb {
        // Thumb content goes here
      }
    }
  5. How UnstyledSlider works

    main

    The UnstyledSlider is a component that represents an interactive range. It is highly customizable through slots:

    • track slot: Renders the visual track of the slider. It is rendered below the thumb.
    • thumb slot: Renders the interactive handle at the current slider offset.

    Both slots receive a SliderState object, which provides access to the current slider state (such as the current value) to allow for reactive styling or content.

    UnstyledSlider(
      value = value,
      onValueChange = onValueChange,
      track = { state -> /* Render track using state */ },
      thumb = { state -> /* Render thumb using state */ },
    )
  6. How scrollbars work in Compose Unstyled

    main

    Scrollbars are composed of three main parts:

    1. ScrollbarState: Represents the scroll position and is connected to the scrollable content's state.
    2. UnstyledVerticalScrollbar / UnstyledHorizontalScrollbar: The container components that render the scrollbar track.
    3. Thumb: The draggable element rendered inside the scrollbar container.

    You connect them by creating a state using rememberScrollbarState(targetState) and passing that state to the scrollbar component.

    val scrollbarState = rememberScrollbarState(scrollState)
    
    UnstyledVerticalScrollbar(scrollbarState) {
      Thumb()
    }
  7. How UnstyledProgress and Indicator work together

    main

    The Progress Indicator is composed of two main parts:

    1. UnstyledProgress(): Represents the visible bounds of the progress, including the track.
    2. Indicator(): A component that fills the available width based on the current progress value.

    To use them, place the Indicator() inside the UnstyledProgress content lambda.

    UnstyledProgress(progress = progress) {
      Indicator()
    }
  8. How Bottom Sheet components work together

    main

    The Bottom Sheet implementation is composed of several key abstractions:

    • SheetDetent: Defines a specific height where the sheet can rest. Detents can be predefined (like SheetDetent.Hidden or SheetDetent.FullyExpanded) or custom.
    • UnstyledBottomSheet: The main draggable container that manages the sheet's state and movement.
    • Sheet: The component that renders the actual visible part of the sheet.
    • DragIndication: An interactive handle added inside the Sheet to provide expand, collapse, and dismiss actions. It is highly recommended for accessibility, as it provides semantic actions for users who cannot drag.
    val sheetState = rememberBottomSheetState(
      initialDetent = SheetDetent.Hidden,
    )
    
    UnstyledBottomSheet(state = sheetState) {
      Sheet {
        DragIndication()
      }
    }
  9. How Modal Bottom Sheet components work together

    main

    The Modal Bottom Sheet is composed of several key abstractions:

    • UnstyledModalBottomSheet: The top-level component representing the modal layer.
    • Sheet: The actual rendered content area of the sheet.
    • SheetDetent: Defines specific height levels where the sheet can rest (e.g., SheetDetent.Hidden, SheetDetent.FullyExpanded).
    • DragIndication: An interactive handle used for expand, collapse, and dismiss actions. It also provides semantic accessibility actions for users to control the sheet without dragging.
    • ModalBottomSheetProperties: Configuration object used to control behavior like IME (keyboard) offset and dismissal logic.
    val sheetState = rememberModalBottomSheetState(
      initialDetent = SheetDetent.Hidden,
    )
    
    UnstyledModalBottomSheet(state = sheetState) {
      Sheet {
        DragIndication()
      }
    }
  10. How to use Compose Unstyled components

    main

    Compose Unstyled is not a standalone component library; it is a set of building blocks used to create your own component library.

    Instead of using a component as a finished UI element, you use it to handle the logic, accessibility, and state management, then apply your own styling to it. This is particularly useful when you need components that behave like standard UI elements (e.g., a ModalBottomSheet) but require a custom look or more flexible API than Material Compose provides.

  11. How Portal and PortalHost work together

    main

    The Portal utility uses a host-destination model to render content from one part of the composition into a shared location:

    • PortalHost: Acts as the destination. It defines the location where all child portal content will be rendered.
    • Portal: Acts as the sender. It sends its content to the nearest available PortalHost in the composition tree. If no PortalHost is found, the Portal renders nothing.

    Rendering Order: Portal content is rendered after the host's own content, but within the same window.

    PortalHost {
      // Host content renders first
      
      Portal {
        // Portal content renders here, after host content
      }
    }
  12. How the Stack component works

    main

    The Stack component is a single layout utility used to arrange children in either a horizontal or vertical orientation.

    Key behaviors:

    • Orientation: Controls whether children are laid out in a row or a column.
    • Spacing: Can apply uniform gaps between children.
    • Alignment: Uses mainAxisArrangement for alignment along the primary direction and crossAxisAlignment for alignment perpendicular to that direction.
    • Space Distribution: Uses the weight() modifier on children to distribute any remaining space in the stack's current orientation.