Skip to main content
Nexus Design System
GitHub

FilterCondition

FilterCondition shows one filter as separate field, operator, value and remove parts. Use it when the user edits a filter from its summary. For a chip that only removes, use FilterChip.

  • Wrap each FilterConditionSegment in a PopoverTrigger or DropdownMenuTrigger with asChild.
  • Your app owns open state, draft values, when changes apply and focus after removal.
  • All parts share one compact height that follows density. There is no size prop.
  • Editor inputs set their own size.
  • Segments are native buttons, so Enter and Space activate them.
  • The full summary stays accessible when it truncates on screen.
  • See Patterns/Filtering for recipes that apply changes at once or on Apply.
Status
'use client';

import {
  FilterCondition,
  FilterConditionField,
  FilterConditionRemove,
  FilterConditionSegment,
} from '@/components/nxds/components/filter-condition/filter-condition';

function ConditionExample({
  field = 'Status',
  operator = 'is',
  value = 'Paid',
  disabled = false,
  onOperatorClick,
  onValueClick,
  onRemove,
}: {
  field?: string;
  operator?: string;
  value?: string;
  disabled?: boolean;
  onOperatorClick?: () => void;
  onValueClick?: () => void;
  onRemove?: () => void;
}) {
  return (
    <FilterCondition>
      <FilterConditionField>{field}</FilterConditionField>
      <FilterConditionSegment
        aria-label={`Edit ${field} operator`}
        className="nx:shrink-0 nx:text-muted-foreground"
        disabled={disabled}
        onClick={onOperatorClick}
      >
        {operator}
      </FilterConditionSegment>
      <FilterConditionSegment
        aria-label={`Edit ${field} value: ${value}`}
        disabled={disabled}
        onClick={onValueClick}
      >
        {value}
      </FilterConditionSegment>
      <FilterConditionRemove
        aria-label={`Remove ${field} filter`}
        disabled={disabled}
        onClick={onRemove}
      />
    </FilterCondition>
  );
}

export default function FilterConditionDefault() {
  return (
    <ConditionExample
      field="Status"
      operator="is"
      value="Paid"
      disabled={false}
    />
  );
}

Installation

npx nxds-cli@latest add filter-condition

Run npx nxds-cli@latest init first if nxds isn’t set up in your app yet. See Install.

Usage

import {
  FilterCondition,
  FilterConditionField,
  FilterConditionRemove,
  FilterConditionSegment,
} from '@/components/nxds/components/filter-condition/filter-condition';

Composition

FilterCondition
├── FilterConditionField
├── FilterConditionSegment
└── FilterConditionRemove

Props

FilterCondition

FilterCondition adds no props of its own; it accepts the props of the element or primitive it wraps.

FilterConditionField

FilterConditionField adds no props of its own; it accepts the props of the element or primitive it wraps.

FilterConditionRemove

FilterConditionRemove props
PropTypeDefaultDescription
aria-labelRequiredstringNo default documented

Defines a string value that labels the current element.

FilterConditionSegment

FilterConditionSegment adds no props of its own; it accepts the props of the element or primitive it wraps.

FilterCondition shows one filter as separate field, operator, value and remove parts. Use it when the user edits a filter from its summary. For a chip that only removes, use FilterChip.

  • Wrap each FilterConditionSegment in a PopoverTrigger or DropdownMenuTrigger with as-child.
  • Your app owns open state, draft values, when changes apply and focus after removal.
  • All parts share one compact height that follows density. There is no size prop.
  • Editor inputs set their own size.
  • Segments are native buttons, so Enter and Space activate them.
  • The full summary stays accessible when it truncates on screen.
  • See Patterns/Filtering for recipes that apply changes at once or on Apply.
<script setup lang="ts">
import {
  FilterCondition,
  FilterConditionField,
  FilterConditionRemove,
  FilterConditionSegment,
} from '@/components/filter-condition';
</script>

<template>
  <FilterCondition data-testid="condition">
    <FilterConditionField>Status</FilterConditionField>
    <FilterConditionSegment
      aria-label="Edit Status operator"
      class="nx:shrink-0 nx:text-muted-foreground"
      >is</FilterConditionSegment
    >
    <FilterConditionSegment
      aria-label="Edit Status value: Paid"
      data-testid="condition-value"
      >Paid</FilterConditionSegment
    >
    <FilterConditionRemove aria-label="Remove Status filter" />
  </FilterCondition>
</template>

Installation

  1. Install these components first:

    Button, ButtonGroup, Spinner.

  2. Copy and paste the following code into your project.

    components/filter-condition/FilterCondition.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface FilterConditionProps {
      class?: HTMLAttributes['class'];
    }
    
    defineOptions({ inheritAttrs: false });
    
    const props = defineProps<FilterConditionProps>();
    </script>
    
    <template>
      <div
        v-bind="$attrs"
        data-slot="filter-condition"
        :class="
          cn(
            'nx:inline-flex nx:max-w-full nx:min-w-0 nx:items-stretch',
            props.class
          )
        "
      >
        <slot />
      </div>
    </template>
    
    components/filter-condition/FilterConditionField.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface FilterConditionFieldProps {
      class?: HTMLAttributes['class'];
    }
    
    defineOptions({ inheritAttrs: false });
    
    const props = defineProps<FilterConditionFieldProps>();
    </script>
    
    <template>
      <span
        v-bind="$attrs"
        data-slot="filter-condition-field"
        :class="
          cn(
            'nx:inline-flex nx:h-(--nx-spacing-8) nx:shrink-0 nx:items-center nx:gap-2 nx:[&_svg]:size-3.5 nx:[&_svg]:shrink-0 nx:rounded-s-base nx:border-default nx:border-border-default nx:bg-container nx:px-2.5 nx:typography-label-default nx:text-muted-foreground',
            props.class
          )
        "
      >
        <slot />
      </span>
    </template>
    
    components/filter-condition/FilterConditionRemove.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { IconX } from '../../lib/icons';
    import { cn } from '../../lib/utils';
    import { buttonVariants } from '../button';
    
    export interface FilterConditionRemoveProps {
      /** Defines a string value that labels the current element. */
      ariaLabel: string;
      class?: HTMLAttributes['class'];
    }
    
    defineOptions({ inheritAttrs: false });
    
    const props = defineProps<FilterConditionRemoveProps>();
    </script>
    
    <template>
      <button
        v-bind="$attrs"
        type="button"
        data-slot="filter-condition-remove"
        :aria-label="ariaLabel"
        :class="
          cn(
            buttonVariants({ variant: 'outline', size: 'icon-sm' }),
            'nx:size-(--nx-spacing-8) nx:shrink-0 nx:rounded-s-none nx:border-s-0 nx:text-muted-foreground nx:active:scale-100 nx:focus-visible:relative nx:focus-visible:z-10',
            props.class
          )
        "
      >
        <IconX aria-hidden="true" />
      </button>
    </template>
    
    components/filter-condition/FilterConditionSegment.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { IconChevronDown } from '../../lib/icons';
    import { cn } from '../../lib/utils';
    import { buttonVariants } from '../button';
    
    export interface FilterConditionSegmentProps {
      class?: HTMLAttributes['class'];
    }
    
    defineOptions({ inheritAttrs: false });
    
    const props = defineProps<FilterConditionSegmentProps>();
    </script>
    
    <template>
      <button
        v-bind="$attrs"
        type="button"
        data-slot="filter-condition-segment"
        :class="
          cn(
            buttonVariants({ variant: 'outline', size: 'sm' }),
            'nx:h-(--nx-spacing-8) nx:min-w-0 nx:rounded-none nx:border-s-0 nx:active:scale-100 nx:focus-visible:relative nx:focus-visible:z-10',
            props.class
          )
        "
      >
        <span class="nx:truncate"><slot /></span>
        <IconChevronDown aria-hidden="true" class="nx:text-muted-foreground" />
      </button>
    </template>
    
    components/filter-condition/index.ts
    export {
      default as FilterCondition,
      type FilterConditionProps,
    } from './FilterCondition.vue';
    export {
      default as FilterConditionField,
      type FilterConditionFieldProps,
    } from './FilterConditionField.vue';
    export {
      default as FilterConditionRemove,
      type FilterConditionRemoveProps,
    } from './FilterConditionRemove.vue';
    export {
      default as FilterConditionSegment,
      type FilterConditionSegmentProps,
    } from './FilterConditionSegment.vue';
    
  3. Keep each file at the path shown above it.

    Paths are relative to packages/vue/src/. Keep the same folders under your @/ root, and the imports between files work without edits.

Usage

import { FilterCondition, FilterConditionField, FilterConditionRemove, FilterConditionSegment } from '@/components/filter-condition';

Props

FilterCondition

FilterCondition adds no props of its own; it accepts the props of the element or primitive it wraps.

FilterConditionField

FilterConditionField adds no props of its own; it accepts the props of the element or primitive it wraps.

FilterConditionRemove

FilterConditionRemove props
PropTypeDefaultDescription
ariaLabelRequiredstringNo default documented

Defines a string value that labels the current element.

FilterConditionSegment

FilterConditionSegment adds no props of its own; it accepts the props of the element or primitive it wraps.