> Markdown version of [Conditional Visibility](https://vaadin.com/docs/next/flow/ui-state/usage-examples/conditional-visibility). Section index: [llms.txt](https://vaadin.com/docs/next/flow/llms.txt)

# Conditional Visibility (since V25.1)

Progressive disclosure is a design pattern where form fields appear or disappear based on previous user selections. This improves user experience by showing only relevant fields and reducing visual clutter. With Signals, you can implement this pattern declaratively using `bindVisible()`.

## <a id="the-pattern"></a>The Pattern

The key to conditional visibility is:

1. Create `ValueSignal` instances to track the values that control visibility

2. Bind form fields to both the data bean (using `Binder`) and to signals (using `bindValue()`)

3. Use `bindVisible()` to show or hide sections based on signal values

This creates a reactive chain: when a user changes a field, the signal updates, which triggers visibility changes in dependent sections.

## <a id="example-visa-application-form"></a>Example: Visa Application Form

This example demonstrates a multi-level visa application form where fields appear progressively based on user selections.

### <a id="setting-up-signals-and-data"></a>Setting Up Signals and Data

First, create the signals that control visibility:

`ConditionalVisibility.java`

```java
// Create binder and data bean
Binder<VisaApplicationData> binder = new Binder<>(
        VisaApplicationData.class);
VisaApplicationData data = new VisaApplicationData();
binder.setBean(data);

// Create signals for conditional visibility
ValueSignal<Boolean> needsVisaSignal = new ValueSignal<>(false);
ValueSignal<VisaType> visaTypeSignal = new ValueSignal<>(VisaType.H1B);
ValueSignal<Boolean> hasH1BPreviouslySignal = new ValueSignal<>(false);
```

Each signal tracks a value that other fields depend on. Initialize them with sensible defaults.

### <a id="level-0-base-question"></a>Level 0: Base Question

The root question controls whether any visa-related fields appear:

`ConditionalVisibility.java`

```java
// Base question: needs visa sponsorship
Checkbox needsVisaCheckbox = new Checkbox(
        "Do you require visa sponsorship?");
binder.forField(needsVisaCheckbox).bind(
        VisaApplicationData::getNeedsVisa,
        VisaApplicationData::setNeedsVisa);
needsVisaCheckbox.bindValue(needsVisaSignal, needsVisaSignal::set);
```

The checkbox is bound to both:

- The data bean via `Binder` - for data persistence

- The signal via `bindValue()` - for reactive visibility control

When the user changes this checkbox, `needsVisaSignal` updates automatically.

### <a id="level-1-conditional-section"></a>Level 1: Conditional Section

The visa section appears only when sponsorship is needed:

`ConditionalVisibility.java`

```java
// Level 1: Visa-related fields (shown when needsVisa is true)
VerticalLayout visaSection = new VerticalLayout();

ComboBox<VisaType> visaTypeSelect = new ComboBox<>("Visa Type",
        VisaType.values());
visaTypeSelect.setValue(VisaType.H1B);
binder.forField(visaTypeSelect).bind(VisaApplicationData::getVisaType,
        VisaApplicationData::setVisaType);
visaTypeSelect.bindValue(visaTypeSignal, visaTypeSignal::set);

TextField currentVisaStatus = new TextField("Current Visa Status");
binder.forField(currentVisaStatus).bind(
        VisaApplicationData::getCurrentVisaStatus,
        VisaApplicationData::setCurrentVisaStatus);

visaSection.add(visaTypeSelect, currentVisaStatus);
visaSection.bindVisible(needsVisaSignal);
```

The `bindVisible(needsVisaSignal)` method creates a reactive binding: the section’s visibility automatically tracks the signal’s boolean value. When `needsVisaSignal` is `true`, the section appears. When `false`, it’s hidden.

The `visaTypeSelect` field is also bound to `visaTypeSignal`, which allows the next level to react to the selected visa type.

### <a id="level-2-nested-conditions"></a>Level 2: Nested Conditions

For more complex conditions, use a lambda expression:

`ConditionalVisibility.java`

```java
// Level 2: H1-B specific fields (shown when visa type is H1B)
VerticalLayout h1bSection = new VerticalLayout();

Checkbox hasH1BPreviouslyCheckbox = new Checkbox(
        "Have you held an H1-B visa before?");
binder.forField(hasH1BPreviouslyCheckbox).bind(
        VisaApplicationData::getHasH1BPreviously,
        VisaApplicationData::setHasH1BPreviously);
hasH1BPreviouslyCheckbox.bindValue(hasH1BPreviouslySignal,
        hasH1BPreviouslySignal::set);

TextField h1bSpecialtyOccupation = new TextField(
        "Specialty Occupation");
// Binder binding omitted for brevity

h1bSection.add(hasH1BPreviouslyCheckbox, h1bSpecialtyOccupation);
h1bSection.bindVisible(() -> needsVisaSignal.get()
        && visaTypeSignal.get() == VisaType.H1B);
```

The lambda `() → needsVisaSignal.get() && visaTypeSignal.get() == VisaType.H1B` reads multiple signals. This section appears only when:

- Visa sponsorship is needed (`needsVisaSignal` is `true`)

- The selected visa type is H1-B

The framework tracks which signals you access in the lambda and automatically updates visibility when any of them change.

### <a id="level-3-multi-level-nesting"></a>Level 3: Multi-Level Nesting

You can nest conditions as deeply as needed:

`ConditionalVisibility.java`

```java
// Level 3: Previous H1-B details (shown when has H1-B previously)
VerticalLayout previousH1BSection = new VerticalLayout();

TextField previousEmployer = new TextField("Previous Employer");
binder.forField(previousEmployer).bind(
        VisaApplicationData::getPreviousEmployer,
        VisaApplicationData::setPreviousEmployer);

TextField previousPetitionNumber = new TextField(
        "Previous Petition Number");
// Additional fields omitted for brevity

previousH1BSection.add(previousEmployer, previousPetitionNumber);
previousH1BSection.bindVisible(() -> needsVisaSignal.get()
        && visaTypeSignal.get() == VisaType.H1B
        && hasH1BPreviouslySignal.get());
```

This section requires three conditions to be `true`:

1. Visa sponsorship is needed

2. Visa type is H1-B

3. User has held H1-B previously

Each level progressively discloses more details, creating a smooth, focused user experience.

## <a id="key-concepts"></a>Key Concepts

### <a id="value-binding"></a>Value Binding

`bindValue()` connects a form field to a signal using two callbacks:

```java
checkbox.bindValue(needsVisaSignal, needsVisaSignal::set);
comboBox.bindValue(visaTypeSignal, visaTypeSignal::set);
```

The method signature is:

```java
field.bindValue(Signal<V> signal, Consumer<V> writeCallback)
```

**Read callback (signal)**: The first parameter is a `Signal<V>`. The framework reads its value using `signal.get()` whenever it needs to update the field. When the signal changes, the field automatically updates.

**Write callback**: The second parameter is a `Consumer<V>` that receives the new value when the user changes the field. Typically, you pass the signal’s setter (e.g., `signal::set`) to update the signal with the new field value, but the callback can also run some validation for input value.

This creates a two-way binding:

- User changes field → write callback is invoked → signal is updated via `set()`

- Signal changes → framework reads via `get()` → field is updated

#### <a id="null-write-callback"></a>Null Write Callback

You can pass `null` as the write callback for read-only bindings:

```java
field.bindValue(mySignal, null);
```

In this case:

- The field displays the signal’s value

- User changes to the field are ignored (the signal is not updated)

- The signal can still be updated programmatically, and the field reflects those changes

This is useful when you want a field to display signal-driven data but prevent user input from modifying that data directly.

### <a id="visibility-binding"></a>Visibility Binding

`bindVisible()` controls component visibility reactively:

```java
// Simple: show when signal is true
component.bindVisible(booleanSignal);

// Complex: show based on multiple conditions
component.bindVisible(() -> signal1.get() && signal2.get() == expectedValue);
```

The framework automatically:

- Tracks which signals are accessed in the lambda

- Re-evaluates visibility when any tracked signal changes

- Updates the component’s visibility accordingly

### <a id="combining-with-binder"></a>Combining with Binder

Use `Binder` alongside signals to maintain data integrity:

```java
Checkbox checkbox = new Checkbox("Label");
binder.forField(checkbox).bind(Data::getValue, Data::setValue); // Data binding
checkbox.bindValue(signal, signal::set); // Reactive binding
```

This dual binding ensures:

- Data flows into your business objects (via `Binder`)

- UI reactivity works correctly (via signals)

## <a id="best-practices"></a>Best Practices

**Create one signal per conditional value**: If three checkboxes control visibility independently, create three boolean signals.

**Initialize signals with defaults**: Set initial values that match your form’s default state to avoid inconsistencies.

**Use lambda expressions for complex conditions**: When visibility depends on multiple signals or computed logic, use `bindVisible(() → …​)`.

**Keep conditions readable**: Extract complex visibility logic into well-named variables or methods if needed.

## <a id="when-to-use-this-pattern"></a>When to Use This Pattern

Use conditional visibility with signals when:

- Form fields should appear or disappear based on user input

- You need progressive disclosure for complex forms

- Multiple levels of nesting are required

- Visibility logic needs to be reactive and automatic

For simple show/hide based on a single button click, regular `setVisible()` may be sufficient. Use signals when the visibility rules are more complex or data-driven.
