diff --git a/frontend/src/metabase-lib/lib/metadata/Field.ts b/frontend/src/metabase-lib/lib/metadata/Field.ts
index 7bb47cc2e120675d3678bb6b1158c594ce3814d7..67a939edfd7ffaa54cb8c54b1c439fab7bdc9ba5 100644
--- a/frontend/src/metabase-lib/lib/metadata/Field.ts
+++ b/frontend/src/metabase-lib/lib/metadata/Field.ts
@@ -50,6 +50,8 @@ class FieldInner extends Base {
   base_type: string | null;
   table?: Table;
   target?: Field;
+  has_field_values?: "list" | "search" | "none";
+  values: any[];
 
   getId() {
     if (Array.isArray(this.id)) {
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.tsx
index b924c73e3f4d4ac10a04b9d4af75619ac40daf74..ee0fe5b1499038c26e90aea740015fa332a92fe5 100644
--- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.tsx
+++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.tsx
@@ -7,6 +7,8 @@ import { isBoolean } from "metabase/lib/schema_metadata";
 
 import { BooleanPickerCheckbox } from "metabase/query_builder/components/filters/pickers/BooleanPicker";
 import { BulkFilterSelect } from "../BulkFilterSelect";
+import { InlineCategoryPicker } from "../InlineCategoryPicker";
+import { SEMANTIC_FIELD_FILTERS, BASE_FIELD_FILTERS } from "./constants";
 
 export interface BulkFilterItemProps {
   query: StructuredQuery;
@@ -25,9 +27,21 @@ export const BulkFilterItem = ({
   onChangeFilter,
   onRemoveFilter,
 }: BulkFilterItemProps): JSX.Element => {
-  const fieldType = useMemo(() => dimension.field().base_type ?? "", [
-    dimension,
-  ]);
+  const fieldType = useMemo(() => {
+    const field = dimension.field();
+
+    if (BASE_FIELD_FILTERS.includes(field.base_type ?? "")) {
+      return field.base_type;
+    }
+
+    if (field.has_field_values === "list") {
+      return "type/Category";
+    }
+
+    if (SEMANTIC_FIELD_FILTERS.includes(field.semantic_type ?? "")) {
+      return field.semantic_type;
+    }
+  }, [dimension]);
 
   const newFilter = useMemo(() => getNewFilter(query, dimension), [
     query,
@@ -55,6 +69,17 @@ export const BulkFilterItem = ({
           onFilterChange={handleChange}
         />
       );
+    case "type/Category":
+      return (
+        <InlineCategoryPicker
+          query={query}
+          filter={filter}
+          newFilter={newFilter}
+          dimension={dimension}
+          onChange={handleChange}
+          onClear={handleClear}
+        />
+      );
     default:
       return (
         <BulkFilterSelect
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.unit.spec.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.unit.spec.tsx
index 9cca900f763fb0c785342ac6776c7d9c6403936e..82d58534b38b9a4d661ba89f05850821905df01e 100644
--- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.unit.spec.tsx
+++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.unit.spec.tsx
@@ -4,6 +4,8 @@
 import React from "react";
 import { render, screen } from "@testing-library/react";
 import { metadata } from "__support__/sample_database_fixture";
+import { getStore } from "__support__/entities-store";
+import { Provider } from "react-redux";
 
 import Field from "metabase-lib/lib/metadata/Field";
 import Filter from "metabase-lib/lib/queries/structured/Filter";
@@ -16,7 +18,7 @@ const booleanField = new Field({
   semantic_type: "",
   table_id: 8,
   name: "bool",
-  has_field_values: "list",
+  has_field_values: "none",
   dimensions: {},
   dimension_options: [],
   effective_type: "type/Boolean",
@@ -30,7 +32,7 @@ const intField = new Field({
   semantic_type: "",
   table_id: 8,
   name: "int_num",
-  has_field_values: "list",
+  has_field_values: "none",
   dimensions: {},
   dimension_options: [],
   effective_type: "type/Integer",
@@ -44,7 +46,7 @@ const floatField = new Field({
   semantic_type: "",
   table_id: 8,
   name: "float_num",
-  has_field_values: "list",
+  has_field_values: "none",
   dimensions: {},
   dimension_options: [],
   effective_type: "type/Float",
@@ -53,9 +55,25 @@ const floatField = new Field({
   metadata,
 });
 
+const categoryField = new Field({
+  database_type: "test",
+  semantic_type: "",
+  table_id: 8,
+  name: "category_string",
+  has_field_values: "list",
+  values: ["Michaelangelo", "Donatello", "Raphael", "Leonardo"],
+  dimensions: {},
+  dimension_options: [],
+  effective_type: "type/Float",
+  id: 137,
+  base_type: "type/Float",
+  metadata,
+});
+
 metadata.fields[booleanField.id] = booleanField;
 metadata.fields[intField.id] = intField;
 metadata.fields[floatField.id] = floatField;
+metadata.fields[categoryField.id] = categoryField;
 
 const card = {
   dataset_query: {
@@ -74,6 +92,7 @@ const query = question.query();
 const booleanDimension = booleanField.dimension();
 const floatDimension = floatField.dimension();
 const intDimension = intField.dimension();
+const categoryDimension = categoryField.dimension();
 
 describe("BulkFilterItem", () => {
   it("renders a boolean picker for a boolean filter", () => {
@@ -149,4 +168,28 @@ describe("BulkFilterItem", () => {
       "float_num",
     );
   });
+
+  it("renders a category picker for category type", () => {
+    const testFilter = new Filter(
+      ["=", ["field", categoryField.id, null], "Donatello"],
+      null,
+      query,
+    );
+    const changeSpy = jest.fn();
+    const store = getStore();
+
+    render(
+      <Provider store={store}>
+        <BulkFilterItem
+          query={query}
+          filter={testFilter}
+          dimension={categoryDimension}
+          onAddFilter={changeSpy}
+          onChangeFilter={changeSpy}
+          onRemoveFilter={changeSpy}
+        />
+      </Provider>,
+    );
+    screen.getByTestId("category-picker");
+  });
 });
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/constants.ts b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/constants.ts
index a91a55c88c8f0a6678c4ea67c1bfee82da706db1..e72728176bac5cb2af363d7dc4bd66f7e35f259e 100644
--- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/constants.ts
+++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/constants.ts
@@ -1 +1,3 @@
-export const INLINE_FIELD_TYPES = ["type/Boolean"];
+export const BASE_FIELD_FILTERS = ["type/Boolean"];
+
+export const SEMANTIC_FIELD_FILTERS = ["type/FK", "type/PK", "type/Category"];
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.styled.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.styled.tsx
index 0dfe24a276316279d455a85d75d8c34909d27311..eae1d6b487000c211741057c0f291bf81cb2e84f 100644
--- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.styled.tsx
+++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.styled.tsx
@@ -8,18 +8,14 @@ export const ListRoot = styled.div`
 `;
 
 export const ListRow = styled.div`
-  display: flex;
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
   padding: 0.375rem 0;
 `;
 
 export const ListRowLabel = styled(Ellipsified)`
-  flex: 1 1 0;
-  margin: 0.625rem 1rem 0.625rem 0;
+  padding: 0.625rem 1rem 0.625rem 0;
   color: ${color("black")};
   line-height: 1rem;
   font-weight: bold;
 `;
-
-export const ListRowContent = styled.div`
-  flex: 1 1 0;
-`;
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.tsx
index 34c8c898d90883229a2a8d924f9fc3558408ce3e..8c195a13ab85d1e4a1a93f1fc8abf524007e6e69 100644
--- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.tsx
+++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.tsx
@@ -12,12 +12,7 @@ import { ModalDivider } from "../BulkFilterModal/BulkFilterModal.styled";
 import Filter from "metabase-lib/lib/queries/structured/Filter";
 import { BulkFilterItem } from "../BulkFilterItem";
 import { SegmentFilterSelect } from "../BulkFilterSelect";
-import {
-  ListRoot,
-  ListRow,
-  ListRowContent,
-  ListRowLabel,
-} from "./BulkFilterList.styled";
+import { ListRoot, ListRow, ListRowLabel } from "./BulkFilterList.styled";
 import { sortDimensions } from "./utils";
 
 export interface BulkFilterListProps {
@@ -91,7 +86,12 @@ const BulkFilterListItem = ({
   onRemoveFilter,
 }: BulkFilterListItemProps): JSX.Element => {
   const options = useMemo(() => {
-    return filters.filter(f => f.dimension()?.isSameBaseDimension(dimension));
+    const filtersForThisDimension = filters.filter(f =>
+      f.dimension()?.isSameBaseDimension(dimension),
+    );
+    return filtersForThisDimension.length
+      ? filtersForThisDimension
+      : [undefined];
   }, [filters, dimension]);
 
   return (
@@ -99,28 +99,17 @@ const BulkFilterListItem = ({
       <ListRowLabel data-testid="dimension-filter-label">
         {dimension.displayName()}
       </ListRowLabel>
-      <ListRowContent>
-        {options.map((filter, index) => (
-          <BulkFilterItem
-            key={index}
-            query={query}
-            filter={filter}
-            dimension={dimension}
-            onAddFilter={onAddFilter}
-            onChangeFilter={onChangeFilter}
-            onRemoveFilter={onRemoveFilter}
-          />
-        ))}
-        {!options.length && (
-          <BulkFilterItem
-            query={query}
-            dimension={dimension}
-            onAddFilter={onAddFilter}
-            onChangeFilter={onChangeFilter}
-            onRemoveFilter={onRemoveFilter}
-          />
-        )}
-      </ListRowContent>
+      {options.map((filter, index) => (
+        <BulkFilterItem
+          key={index}
+          query={query}
+          filter={filter}
+          dimension={dimension}
+          onAddFilter={onAddFilter}
+          onChangeFilter={onChangeFilter}
+          onRemoveFilter={onRemoveFilter}
+        />
+      ))}
     </ListRow>
   );
 };
@@ -143,7 +132,7 @@ const SegmentListItem = ({
   <>
     <ListRow>
       <ListRowLabel>{t`Segments`}</ListRowLabel>
-      <ListRowContent>
+      <>
         <SegmentFilterSelect
           query={query}
           segments={segments}
@@ -151,7 +140,7 @@ const SegmentListItem = ({
           onRemoveFilter={onRemoveFilter}
           onClearSegments={onClearSegments}
         />
-      </ListRowContent>
+      </>
     </ListRow>
     <ModalDivider marginY="0.5rem" />
   </>
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterSelect/BulkFilterSelect.styled.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterSelect/BulkFilterSelect.styled.tsx
index a670e83e8cc25e3b0f8f4ca841b4934c6ade6d1b..c49cf9a5de0800662518bcc0248c707f928d335b 100644
--- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterSelect/BulkFilterSelect.styled.tsx
+++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterSelect/BulkFilterSelect.styled.tsx
@@ -4,6 +4,7 @@ import FilterPopover from "../../FilterPopover";
 import Select from "metabase/core/components/Select";
 
 export const SelectFilterButton = styled(SelectButton)`
+  grid-column: 2;
   min-height: 2.25rem;
 
   &:not(:first-of-type) {
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/InlineCategoryPicker.styled.tsx b/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/InlineCategoryPicker.styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ed4242330182ebb35b4f0e9459a89f5516caa347
--- /dev/null
+++ b/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/InlineCategoryPicker.styled.tsx
@@ -0,0 +1,27 @@
+import styled from "@emotion/styled";
+import { color } from "metabase/lib/colors";
+import { space } from "metabase/styled-components/theme";
+
+import LoadingSpinner from "metabase/components/LoadingSpinner";
+
+export const Loading = styled(LoadingSpinner)`
+  margin: ${space(1)} 0;
+  color: ${color("brand")};
+`;
+
+export const PickerContainer = styled.div`
+  grid-column: span 2;
+  margin: ${space(2)} 0;
+  padding-bottom: ${space(2)};
+  font-weight: bold;
+  border-bottom: 1px solid ${color("border")};
+`;
+
+export const PickerGrid = styled.div`
+  width: 100%;
+  display: grid;
+  columns: 2;
+  align-items: center;
+  grid-template-columns: repeat(3, 1fr);
+  gap: ${space(2)};
+`;
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/InlineCategoryPicker.tsx b/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/InlineCategoryPicker.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..793f4c123b0c69a5812fd88191f6535e375e920d
--- /dev/null
+++ b/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/InlineCategoryPicker.tsx
@@ -0,0 +1,154 @@
+import React, { useState, useEffect } from "react";
+import { connect } from "react-redux";
+import { t } from "ttag";
+
+import Filter from "metabase-lib/lib/queries/structured/Filter";
+import Fields from "metabase/entities/fields";
+import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
+import Dimension from "metabase-lib/lib/Dimension";
+import { useSafeAsyncFunction } from "metabase/hooks/use-safe-async-function";
+
+import Warnings from "metabase/query_builder/components/Warnings";
+import Checkbox from "metabase/core/components/CheckBox";
+
+import { MAX_INLINE_CATEGORIES } from "./constants";
+import {
+  PickerContainer,
+  PickerGrid,
+  Loading,
+} from "./InlineCategoryPicker.styled";
+import { BulkFilterSelect } from "../BulkFilterSelect";
+
+const mapStateToProps = (state: any, props: any) => {
+  const fieldId = props.dimension?.field?.()?.id;
+  const fieldValues =
+    fieldId != null
+      ? Fields.selectors.getFieldValues(state, {
+          entityId: fieldId,
+        })
+      : [];
+  return { fieldValues };
+};
+
+const mapDispatchToProps = {
+  fetchFieldValues: Fields.actions.fetchFieldValues,
+};
+
+interface InlineCategoryPickerProps {
+  query: StructuredQuery;
+  filter?: Filter;
+  newFilter: Filter;
+  dimension: Dimension;
+  fieldValues: any[];
+  fetchFieldValues: ({ id }: { id: number }) => Promise<any>;
+  onChange: (newFilter: Filter) => void;
+  onClear: () => void;
+}
+
+export function InlineCategoryPickerComponent({
+  query,
+  filter,
+  newFilter,
+  dimension,
+  fieldValues,
+  fetchFieldValues,
+  onChange,
+  onClear,
+}: InlineCategoryPickerProps) {
+  const safeFetchFieldValues = useSafeAsyncFunction(fetchFieldValues);
+  const shouldFetchFieldValues = !dimension?.field()?.hasFieldValues();
+  const [isLoading, setIsLoading] = useState(shouldFetchFieldValues);
+  const [hasError, setHasError] = useState(false);
+
+  useEffect(() => {
+    if (!shouldFetchFieldValues) {
+      setIsLoading(false);
+      return;
+    }
+    const field = dimension.field();
+    safeFetchFieldValues({ id: field.id })
+      .then(() => {
+        setIsLoading(false);
+      })
+      .catch(() => {
+        setHasError(true);
+      });
+  }, [dimension, safeFetchFieldValues, shouldFetchFieldValues]);
+
+  if (hasError) {
+    return (
+      <Warnings
+        warnings={[
+          t`There was an error loading the field values for this field`,
+        ]}
+      />
+    );
+  }
+
+  if (isLoading) {
+    return <Loading size={20} />;
+  }
+
+  if (fieldValues.length <= MAX_INLINE_CATEGORIES) {
+    return (
+      <SimpleCategoryFilterPicker
+        filter={filter ?? newFilter}
+        onChange={onChange}
+        options={fieldValues.flat()}
+      />
+    );
+  }
+
+  return (
+    <BulkFilterSelect
+      query={query}
+      filter={filter}
+      dimension={dimension}
+      handleChange={onChange}
+      handleClear={onClear}
+    />
+  );
+}
+
+interface SimpleCategoryFilterPickerProps {
+  filter: Filter;
+  options: (string | number)[];
+  onChange: (newFilter: Filter) => void;
+}
+
+export function SimpleCategoryFilterPicker({
+  filter,
+  options,
+  onChange,
+}: SimpleCategoryFilterPickerProps) {
+  const filterValues = filter.arguments().filter(Boolean);
+
+  const handleChange = (option: string | number, checked: boolean) => {
+    const newArgs = checked
+      ? [...filterValues, option]
+      : filterValues.filter(filterValue => filterValue !== option);
+
+    onChange(filter.setArguments(newArgs));
+  };
+
+  return (
+    <PickerContainer data-testid="category-picker">
+      <PickerGrid>
+        {options.map((option: string | number) => (
+          <Checkbox
+            key={option.toString()}
+            checked={filterValues.includes(option)}
+            onChange={e => handleChange(option, e.target.checked)}
+            checkedColor="accent2"
+            label={option.toString()}
+          />
+        ))}
+      </PickerGrid>
+    </PickerContainer>
+  );
+}
+
+export const InlineCategoryPicker = connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(InlineCategoryPickerComponent);
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/InlineCategoryPicker.unit.spec.tsx b/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/InlineCategoryPicker.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..89ebac2e8649fede6c3a084ec355d54f4ccabddc
--- /dev/null
+++ b/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/InlineCategoryPicker.unit.spec.tsx
@@ -0,0 +1,341 @@
+/* eslint-disable @typescript-eslint/ban-ts-comment */
+
+import React from "react";
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+import { metadata } from "__support__/sample_database_fixture";
+
+import Field from "metabase-lib/lib/metadata/Field";
+import Filter from "metabase-lib/lib/queries/structured/Filter";
+import Question from "metabase-lib/lib/Question";
+import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
+
+import { InlineCategoryPickerComponent } from "./InlineCategoryPicker";
+import { MAX_INLINE_CATEGORIES } from "./constants";
+
+const smallCategoryField = new Field({
+  database_type: "test",
+  semantic_type: "type/Category",
+  effective_type: "type/Text",
+  base_type: "type/Text",
+  table_id: 8,
+  name: "small_category_field",
+  has_field_values: "list",
+  values: [["Michaelangelo"], ["Donatello"], ["Raphael"], ["Leonardo"]],
+  dimensions: {},
+  dimension_options: [],
+  id: 137,
+  metadata,
+});
+
+// we want to make sure we always get enough unique field values
+// even if we change MAX_INLINE_CATEGORIES
+const turtleFactory = () => {
+  const name = ["Michaelangelo", "Donatello", "Raphael", "Leonardo"][
+    Math.floor(Math.random() * 4)
+  ];
+  return [`${name}_${Math.round(Math.random() * 100000)}`];
+};
+
+const largeCategoryField = new Field({
+  database_type: "test",
+  semantic_type: "type/Category",
+  effective_type: "type/Text",
+  base_type: "type/Text",
+  table_id: 8,
+  name: "large_category_field",
+  has_field_values: "list",
+  values: new Array(MAX_INLINE_CATEGORIES + 1).fill(null).map(turtleFactory),
+  dimensions: {},
+  dimension_options: [],
+  id: 138,
+  metadata,
+});
+
+const emptyCategoryField = new Field({
+  database_type: "test",
+  semantic_type: "type/Category",
+  effective_type: "type/Text",
+  base_type: "type/Text",
+  table_id: 8,
+  name: "empty_category_field",
+  has_field_values: "list",
+  values: [],
+  dimensions: {},
+  dimension_options: [],
+  id: 139,
+  metadata,
+});
+
+// @ts-ignore
+metadata.fields[smallCategoryField.id] = smallCategoryField;
+// @ts-ignore
+metadata.fields[largeCategoryField.id] = largeCategoryField;
+// @ts-ignore
+metadata.fields[emptyCategoryField.id] = emptyCategoryField;
+
+const card = {
+  dataset_query: {
+    database: 5,
+    query: {
+      "source-table": 8,
+    },
+    type: "query",
+  },
+  display: "table",
+  visualization_settings: {},
+};
+
+const question = new Question(card, metadata);
+const query = question.query() as StructuredQuery;
+const smallDimension = smallCategoryField.dimension();
+const largeDimension = largeCategoryField.dimension();
+const emptyDimension = emptyCategoryField.dimension();
+
+describe("InlineCategoryPicker", () => {
+  it("should render an inline category picker", () => {
+    const testFilter = new Filter(
+      ["=", ["field", smallCategoryField.id, null], undefined],
+      null,
+      query,
+    );
+    const changeSpy = jest.fn();
+    const fetchSpy = jest.fn();
+
+    render(
+      <InlineCategoryPickerComponent
+        query={query}
+        filter={testFilter}
+        newFilter={testFilter}
+        onChange={changeSpy}
+        fieldValues={smallCategoryField.values}
+        fetchFieldValues={fetchSpy}
+        dimension={smallDimension}
+        onClear={changeSpy}
+      />,
+    );
+
+    screen.getByTestId("category-picker");
+    smallCategoryField.values.forEach(([value]) => {
+      screen.getByText(value);
+    });
+  });
+
+  it("should render a loading spinner while loading", async () => {
+    const testFilter = new Filter(
+      ["=", ["field", emptyCategoryField.id, null], undefined],
+      null,
+      query,
+    );
+    const changeSpy = jest.fn();
+    const fetchSpy = jest.fn();
+
+    render(
+      <InlineCategoryPickerComponent
+        query={query}
+        filter={testFilter}
+        newFilter={testFilter}
+        onChange={changeSpy}
+        fieldValues={emptyCategoryField.values}
+        fetchFieldValues={fetchSpy}
+        dimension={emptyDimension}
+        onClear={changeSpy}
+      />,
+    );
+    screen.getByTestId("loading-spinner");
+    await waitFor(() => expect(fetchSpy).toHaveBeenCalled());
+  });
+
+  it("should render a warning message on api failure", async () => {
+    const testFilter = new Filter(
+      ["=", ["field", emptyCategoryField.id, null], undefined],
+      null,
+      query,
+    );
+    const changeSpy = jest.fn();
+    const fetchSpy = jest.fn();
+
+    render(
+      <InlineCategoryPickerComponent
+        query={query}
+        filter={testFilter}
+        newFilter={testFilter}
+        onChange={changeSpy}
+        fieldValues={emptyCategoryField.values}
+        fetchFieldValues={fetchSpy}
+        dimension={emptyDimension}
+        onClear={changeSpy}
+      />,
+    );
+    await waitFor(() => expect(fetchSpy).toHaveBeenCalled());
+    screen.getByLabelText("warning icon");
+  });
+
+  it(`should render up to ${MAX_INLINE_CATEGORIES} checkboxes`, () => {
+    const testFilter = new Filter(
+      ["=", ["field", smallCategoryField.id, null], undefined],
+      null,
+      query,
+    );
+    const changeSpy = jest.fn();
+    const fetchSpy = jest.fn();
+
+    render(
+      <InlineCategoryPickerComponent
+        query={query}
+        filter={testFilter}
+        newFilter={testFilter}
+        onChange={changeSpy}
+        fieldValues={smallCategoryField.values}
+        fetchFieldValues={fetchSpy}
+        dimension={smallDimension}
+        onClear={changeSpy}
+      />,
+    );
+
+    screen.getByTestId("category-picker");
+    smallCategoryField.values.forEach(([value]) => {
+      screen.getByText(value);
+    });
+  });
+
+  it(`should not render more than ${MAX_INLINE_CATEGORIES} checkboxes`, () => {
+    const testFilter = new Filter(
+      ["=", ["field", largeCategoryField.id, null], undefined],
+      null,
+      query,
+    );
+    const changeSpy = jest.fn();
+    const fetchSpy = jest.fn();
+
+    render(
+      <InlineCategoryPickerComponent
+        query={query}
+        filter={testFilter}
+        newFilter={testFilter}
+        onChange={changeSpy}
+        fieldValues={largeCategoryField.values}
+        fetchFieldValues={fetchSpy}
+        dimension={largeDimension}
+        onClear={changeSpy}
+      />,
+    );
+
+    expect(screen.queryByTestId("category-picker")).not.toBeInTheDocument();
+    // should render general purpose picker instead
+    screen.getByTestId("select-button");
+  });
+
+  it("should load existing filter selections", () => {
+    const testFilter = new Filter(
+      ["=", ["field", smallCategoryField.id, null], "Donatello", "Leonardo"],
+      null,
+      query,
+    );
+    const changeSpy = jest.fn();
+    const fetchSpy = jest.fn();
+
+    render(
+      <InlineCategoryPickerComponent
+        query={query}
+        filter={testFilter}
+        newFilter={testFilter}
+        onChange={changeSpy}
+        fieldValues={smallCategoryField.values}
+        fetchFieldValues={fetchSpy}
+        dimension={smallDimension}
+        onClear={changeSpy}
+      />,
+    );
+
+    screen.getByTestId("category-picker");
+    expect(screen.getByLabelText("Donatello")).toBeChecked();
+    expect(screen.getByLabelText("Leonardo")).toBeChecked();
+    expect(screen.getByLabelText("Raphael")).not.toBeChecked();
+    expect(screen.getByLabelText("Michaelangelo")).not.toBeChecked();
+  });
+
+  it("should save a filter based on selection", () => {
+    const testFilter = new Filter(
+      ["=", ["field", smallCategoryField.id, null], undefined],
+      null,
+      query,
+    );
+    const changeSpy = jest.fn();
+    const fetchSpy = jest.fn();
+
+    render(
+      <InlineCategoryPickerComponent
+        query={query}
+        filter={testFilter}
+        newFilter={testFilter}
+        onChange={changeSpy}
+        fieldValues={smallCategoryField.values}
+        fetchFieldValues={fetchSpy}
+        dimension={smallDimension}
+        onClear={changeSpy}
+      />,
+    );
+
+    screen.getByTestId("category-picker");
+    userEvent.click(screen.getByLabelText("Raphael"));
+    expect(changeSpy.mock.calls.length).toBe(1);
+    expect(changeSpy.mock.calls[0][0]).toEqual([
+      "=",
+      ["field", 137, null],
+      "Raphael",
+    ]);
+  });
+
+  it("should fetch field values data if its not already loaded", async () => {
+    const testFilter = new Filter(
+      ["=", ["field", emptyCategoryField.id, null], undefined],
+      null,
+      query,
+    );
+    const changeSpy = jest.fn();
+    const fetchSpy = jest.fn();
+
+    render(
+      <InlineCategoryPickerComponent
+        query={query}
+        filter={testFilter}
+        newFilter={testFilter}
+        onChange={changeSpy}
+        fieldValues={emptyCategoryField.values}
+        fetchFieldValues={fetchSpy}
+        dimension={emptyDimension}
+        onClear={changeSpy}
+      />,
+    );
+    await waitFor(() => expect(fetchSpy).toHaveBeenCalled());
+
+    expect(fetchSpy.mock.calls[0][0]).toEqual({ id: emptyCategoryField.id });
+  });
+
+  it("should not fetch field values data if it is already present", async () => {
+    const testFilter = new Filter(
+      ["=", ["field", largeCategoryField.id, null], undefined],
+      null,
+      query,
+    );
+    const changeSpy = jest.fn();
+    const fetchSpy = jest.fn();
+
+    render(
+      <InlineCategoryPickerComponent
+        query={query}
+        filter={testFilter}
+        newFilter={testFilter}
+        onChange={changeSpy}
+        fieldValues={largeCategoryField.values}
+        fetchFieldValues={fetchSpy}
+        dimension={largeDimension}
+        onClear={changeSpy}
+      />,
+    );
+
+    expect(fetchSpy).not.toHaveBeenCalled();
+  });
+});
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/constants.ts b/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..79cbbff23ce9b7ad79fdcdac423560553fc10168
--- /dev/null
+++ b/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/constants.ts
@@ -0,0 +1 @@
+export const MAX_INLINE_CATEGORIES = 12;
diff --git a/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/index.ts b/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b6c85ed4e85e0e425127b55708c1471a7d983eeb
--- /dev/null
+++ b/frontend/src/metabase/query_builder/components/filters/modals/InlineCategoryPicker/index.ts
@@ -0,0 +1 @@
+export * from "./InlineCategoryPicker";
diff --git a/frontend/test/metabase/scenarios/filters/filter-bulk.cy.spec.js b/frontend/test/metabase/scenarios/filters/filter-bulk.cy.spec.js
index 3e74148596bf475fc5c6ff8d999e7f80dc13bc89..b41150fe3c72b7a1efb1ba796f6772716cf89366 100644
--- a/frontend/test/metabase/scenarios/filters/filter-bulk.cy.spec.js
+++ b/frontend/test/metabase/scenarios/filters/filter-bulk.cy.spec.js
@@ -8,7 +8,7 @@ import {
 import { SAMPLE_DB_ID } from "__support__/e2e/cypress_data";
 import { SAMPLE_DATABASE } from "__support__/e2e/cypress_sample_database";
 
-const { ORDERS_ID, ORDERS } = SAMPLE_DATABASE;
+const { ORDERS_ID, ORDERS, PEOPLE_ID } = SAMPLE_DATABASE;
 
 const rawQuestionDetails = {
   dataset_query: {
@@ -20,6 +20,16 @@ const rawQuestionDetails = {
   },
 };
 
+const peopleQuestion = {
+  dataset_query: {
+    database: SAMPLE_DB_ID,
+    type: "query",
+    query: {
+      "source-table": PEOPLE_ID,
+    },
+  },
+};
+
 const filteredQuestionDetails = {
   dataset_query: {
     database: SAMPLE_DB_ID,
@@ -136,15 +146,7 @@ describe("scenarios > filters > bulk filtering", () => {
 
     modal().within(() => {
       cy.findByText("Product").click();
-      cy.findByLabelText("Category").click();
-    });
-
-    popover().within(() => {
-      cy.findByText("Gadget").click();
-      cy.button("Add filter").click();
-    });
-
-    modal().within(() => {
+      cy.findByLabelText("Gadget").click();
       cy.button("Apply").click();
       cy.wait("@dataset");
     });
@@ -401,6 +403,42 @@ describe("scenarios > filters > bulk filtering", () => {
       });
     });
   });
+  describe("category filters", () => {
+    beforeEach(() => {
+      visitQuestionAdhoc(peopleQuestion);
+      openFilterModal();
+    });
+
+    it("should show inline category picker for referral source", () => {
+      modal().within(() => {
+        cy.findByText("Affiliate").click();
+        cy.button("Apply").click();
+        cy.wait("@dataset");
+      });
+
+      cy.findByText("Source is Affiliate").should("be.visible");
+      cy.findByText("Showing 506 rows").should("be.visible");
+    });
+
+    it("should not show inline category picker for state", () => {
+      modal().within(() => {
+        cy.findByLabelText("State").click();
+      });
+
+      popover().within(() => {
+        cy.findByText("AZ").click();
+        cy.button("Add filter").click();
+      });
+
+      modal().within(() => {
+        cy.button("Apply").click();
+        cy.wait("@dataset");
+      });
+
+      cy.findByText("State is AZ").should("be.visible");
+      cy.findByText("Showing 20 rows").should("be.visible");
+    });
+  });
 });
 
 const modal = () => {