Logoheadless

Users Cookbook#

This cookbook contains copy-paste ready recipes. Each example is minimal and can be dropped into a Flutter project that already uses a Headless preset.

If you need more:

  • TextField: docs/users/COOKBOOK_TEXTFIELD.md
  • Autocomplete: docs/users/COOKBOOK_AUTOCOMPLETE.md
  • Advanced (scopes + defaults): docs/users/COOKBOOK_ADVANCED.md
  • Guardrails: docs/users/GUARDRAILS.md

Button style#

Use style: for quick per-instance customization.

import 'package:flutter/widgets.dart';
import 'package:headless_button/headless_button.dart';

class SaveButton extends StatelessWidget {
  const SaveButton({super.key});

  @override
  Widget build(BuildContext context) {
    return RTextButton(
      onPressed: () {},
      style: const RButtonStyle(
        backgroundColor: Color(0xFF0066FF),
        foregroundColor: Color(0xFFFFFFFF),
        radius: 12,
      ),
      child: const Text('Save'),
    );
  }
}

Button slots (decorate surface)#

Use slots: for partial customization without reimplementing a renderer.

import 'package:flutter/widgets.dart';
import 'package:headless_button/headless_button.dart';
import 'package:headless_contracts/headless_contracts.dart';

class FancyButton extends StatelessWidget {
  const FancyButton({super.key});

  @override
  Widget build(BuildContext context) {
    return RTextButton(
      onPressed: () {},
      slots: RButtonSlots(
        surface: Decorate(
          (ctx, child) => DecoratedBox(
            decoration: const BoxDecoration(
              gradient: LinearGradient(
                colors: [Color(0xFF4B6FFF), Color(0xFF7B4BFF)],
              ),
            ),
            child: child,
          ),
        ),
      ),
      child: const Text('Upgrade'),
    );
  }
}

Use RDropdownButton<T> with an item adapter. This is the select-like control.

import 'package:flutter/widgets.dart';
import 'package:headless_dropdown_button/headless_dropdown_button.dart';
import 'package:headless_foundation/headless_foundation.dart';

class CityDropdown extends StatelessWidget {
  const CityDropdown({super.key});

  @override
  Widget build(BuildContext context) {
    return RDropdownButton<String>(
      items: const ['Paris', 'Berlin', 'Tokyo'],
      itemAdapter: HeadlessItemAdapter.simple(
        id: (v) => ListboxItemId(v),
        titleText: (v) => v,
      ),
      value: 'Paris',
      onChanged: (_) {},
    );
  }
}

If you only need to change colors / radius, use style: (token-level).

import 'package:flutter/widgets.dart';
import 'package:headless_dropdown_button/headless_dropdown_button.dart';
import 'package:headless_foundation/headless_foundation.dart';

class CityDropdown extends StatelessWidget {
  const CityDropdown({super.key});

  @override
  Widget build(BuildContext context) {
    return RDropdownButton<String>(
      items: const ['Paris', 'Berlin', 'Tokyo'],
      itemAdapter: HeadlessItemAdapter.simple(
        id: (v) => ListboxItemId(v),
        titleText: (v) => v,
      ),
      value: 'Paris',
      onChanged: (_) {},
      style: const RDropdownStyle(
        menuBackgroundColor: Color(0xFFFFFFFF),
        menuBorderRadius: BorderRadius.all(Radius.circular(12)),
      ),
    );
  }
}

If you need to wrap structure (clip/shadow/animation/custom surface), use menuSurface: Decorate(...).

import 'package:flutter/widgets.dart';
import 'package:headless_contracts/headless_contracts.dart';
import 'package:headless_dropdown_button/headless_dropdown_button.dart';
import 'package:headless_foundation/headless_foundation.dart';

class CityDropdown extends StatelessWidget {
  const CityDropdown({super.key});

  @override
  Widget build(BuildContext context) {
    return RDropdownButton<String>(
      items: const ['Paris', 'Berlin', 'Tokyo'],
      itemAdapter: HeadlessItemAdapter.simple(
        id: (v) => ListboxItemId(v),
        titleText: (v) => v,
      ),
      value: 'Paris',
      onChanged: (_) {},
      slots: RDropdownButtonSlots(
        menuSurface: Decorate(
          (ctx, child) => ClipRRect(
            borderRadius: const BorderRadius.all(Radius.circular(12)),
            child: child,
          ),
        ),
      ),
    );
  }
}

Checkbox slots (decorate box)#

import 'package:flutter/widgets.dart';
import 'package:headless_checkbox/headless_checkbox.dart';
import 'package:headless_contracts/headless_contracts.dart';

class ConsentCheckbox extends StatelessWidget {
  const ConsentCheckbox({super.key, required this.value, required this.onChanged});

  final bool value;
  final ValueChanged<bool?> onChanged;

  @override
  Widget build(BuildContext context) {
    return RCheckbox(
      value: value,
      onChanged: onChanged,
      slots: RCheckboxSlots(
        box: Decorate(
          (ctx, child) => ClipRRect(
            borderRadius: const BorderRadius.all(Radius.circular(6)),
            child: child,
          ),
        ),
      ),
    );
  }
}

This keeps behavior intact, but changes visuals for multi-select rows.

import 'package:flutter/material.dart';
import 'package:headless_contracts/headless_contracts.dart';
import 'package:headless_dropdown_button/headless_dropdown_button.dart';
import 'package:headless_foundation/headless_foundation.dart';

class MultiSelectCityDropdown extends StatelessWidget {
  const MultiSelectCityDropdown({super.key});

  @override
  Widget build(BuildContext context) {
    return RDropdownButton<String>(
      items: const ['Paris', 'Berlin', 'Tokyo'],
      itemAdapter: HeadlessItemAdapter.simple(
        id: (v) => ListboxItemId(v),
        titleText: (v) => v,
      ),
      selectedValues: const ['Paris'],
      onSelectionChanged: (_) {},
      slots: RDropdownButtonSlots(
        itemContent: Decorate(
          (ctx, child) => Row(
            children: [
              IgnorePointer(
                child: Checkbox(
                  value: ctx.isSelected,
                  onChanged: null,
                ),
              ),
              const SizedBox(width: 8),
              Expanded(child: child),
            ],
          ),
        ),
      ),
    );
  }
}

CheckboxListTile tile slot (decorate)#

Wrap the default tile without taking over activation.

import 'package:flutter/widgets.dart';
import 'package:headless_checkbox/headless_checkbox.dart';
import 'package:headless_contracts/headless_contracts.dart';

class ConsentTile extends StatelessWidget {
  const ConsentTile({super.key, required this.value, required this.onChanged});

  final bool value;
  final ValueChanged<bool?> onChanged;

  @override
  Widget build(BuildContext context) {
    return RCheckboxListTile(
      value: value,
      onChanged: onChanged,
      title: const Text('I agree'),
      slots: RCheckboxListTileSlots(
        tile: Decorate(
          (ctx, child) => Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12),
            child: child,
          ),
        ),
      ),
    );
  }
}

SafeDropdownRenderer (full takeover, minimal template)#

Use this when you need a fully custom dropdown renderer but still want safe defaults.

import 'package:flutter/widgets.dart';
import 'package:headless_contracts/headless_contracts.dart';

final safe = SafeDropdownRenderer(
  buildTrigger: (ctx) => ctx.child,
  buildMenuSurface: (ctx) => ctx.child,
  buildItem: (ctx) => ctx.child,
);