diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx
index bd313b231e7..b74dc5258d0 100644
--- a/packages/grafana-ui/src/components/Select/SelectBase.tsx
+++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx
@@ -128,6 +128,7 @@ export function SelectBase<T>({
   onInputChange,
   onKeyDown,
   onOpenMenu,
+  onFocus,
   openMenuOnFocus = false,
   options = [],
   placeholder = 'Choose',
@@ -235,6 +236,7 @@ export function SelectBase<T>({
     onKeyDown,
     onMenuClose: onCloseMenu,
     onMenuOpen: onOpenMenu,
+    onFocus,
     formatOptionLabel,
     openMenuOnFocus,
     options,
diff --git a/packages/grafana-ui/src/components/Select/types.ts b/packages/grafana-ui/src/components/Select/types.ts
index 4b93366dc86..12606c768c0 100644
--- a/packages/grafana-ui/src/components/Select/types.ts
+++ b/packages/grafana-ui/src/components/Select/types.ts
@@ -61,6 +61,7 @@ export interface SelectCommonProps<T> {
   onInputChange?: (value: string, actionMeta: InputActionMeta) => void;
   onKeyDown?: (event: React.KeyboardEvent) => void;
   onOpenMenu?: () => void;
+  onFocus?: () => void;
   openMenuOnFocus?: boolean;
   options?: Array<SelectableValue<T>>;
   placeholder?: string;
diff --git a/public/app/core/components/TagFilter/TagFilter.tsx b/public/app/core/components/TagFilter/TagFilter.tsx
index c69fd5ac889..c419f6a8db7 100644
--- a/public/app/core/components/TagFilter/TagFilter.tsx
+++ b/public/app/core/components/TagFilter/TagFilter.tsx
@@ -1,11 +1,9 @@
-// Libraries
-import React, { FC } from 'react';
+import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
 import { css } from '@emotion/css';
 import { components } from 'react-select';
-import debounce from 'debounce-promise';
-import { stylesFactory, useTheme, Icon, AsyncMultiSelect } from '@grafana/ui';
-import { escapeStringForRegex, GrafanaTheme } from '@grafana/data';
-// Components
+import { Icon, MultiSelect, useStyles2 } from '@grafana/ui';
+import { escapeStringForRegex, GrafanaTheme2 } from '@grafana/data';
+
 import { TagOption } from './TagOption';
 import { TagBadge } from './TagBadge';
 
@@ -14,6 +12,12 @@ export interface TermCount {
   count: number;
 }
 
+interface TagSelectOption {
+  value: string;
+  label: string;
+  count: number;
+}
+
 export interface Props {
   allowCustomValue?: boolean;
   formatCreateLabel?: (input: string) => string;
@@ -45,29 +49,70 @@ export const TagFilter: FC<Props> = ({
   tags,
   width,
 }) => {
-  const theme = useTheme();
-  const styles = getStyles(theme);
+  const styles = useStyles2(getStyles);
 
-  const onLoadOptions = async (query: string) => {
+  const currentlySelectedTags = tags.map((tag) => ({ value: tag, label: tag, count: 0 }));
+  const [options, setOptions] = useState<TagSelectOption[]>(currentlySelectedTags);
+  const [isLoading, setIsLoading] = useState(false);
+  const [previousTags, setPreviousTags] = useState(tags);
+
+  // Necessary to force re-render to keep tag options up to date / relevant
+  const selectKey = useMemo(() => tags.join(), [tags]);
+
+  const onLoadOptions = useCallback(async () => {
     const options = await tagOptions();
-    return options.map((option) => ({
-      value: option.term,
-      label: option.term,
-      count: option.count,
-    }));
-  };
+    return options.map((option) => {
+      if (tags.includes(option.term)) {
+        return {
+          value: option.term,
+          label: option.term,
+          count: 0,
+        };
+      } else {
+        return {
+          value: option.term,
+          label: option.term,
+          count: option.count,
+        };
+      }
+    });
+  }, [tagOptions, tags]);
 
-  const debouncedLoadOptions = debounce(onLoadOptions, 300);
+  const onFocus = useCallback(async () => {
+    setIsLoading(true);
+    const results = await onLoadOptions();
+    setOptions(results);
+    setIsLoading(false);
+  }, [onLoadOptions]);
+
+  useEffect(() => {
+    // Load options when tag is selected externally
+    if (tags.length > 0 && options.length === 0) {
+      onFocus();
+    }
+  }, [onFocus, options.length, tags.length]);
+
+  useEffect(() => {
+    // Update selected tags to not include (counts) when selected externally
+    if (tags !== previousTags) {
+      setPreviousTags(tags);
+      onFocus();
+    }
+  }, [onFocus, previousTags, tags]);
 
   const onTagChange = (newTags: any[]) => {
     // On remove with 1 item returns null, so we need to make sure it's an empty array in that case
     // https://github.com/JedWatson/react-select/issues/3632
+    newTags.forEach((tag) => (tag.count = 0));
+
     onChange((newTags || []).map((tag) => tag.value));
   };
 
-  const value = tags.map((tag) => ({ value: tag, label: tag, count: 0 }));
-
   const selectOptions = {
+    key: selectKey,
+    onFocus,
+    isLoading,
+    options,
     allowCreateWhileLoading: true,
     allowCustomValue,
     formatCreateLabel,
@@ -77,12 +122,11 @@ export const TagFilter: FC<Props> = ({
     getOptionValue: (i: any) => i.value,
     inputId,
     isMulti: true,
-    loadOptions: debouncedLoadOptions,
     loadingMessage: 'Loading...',
     noOptionsMessage: 'No tags found',
     onChange: onTagChange,
     placeholder,
-    value,
+    value: currentlySelectedTags,
     width,
     components: {
       Option: TagOption,
@@ -109,37 +153,35 @@ export const TagFilter: FC<Props> = ({
           Clear tags
         </span>
       )}
-      <AsyncMultiSelect menuShouldPortal {...selectOptions} prefix={<Icon name="tag-alt" />} aria-label="Tag filter" />
+      <MultiSelect menuShouldPortal {...selectOptions} prefix={<Icon name="tag-alt" />} aria-label="Tag filter" />
     </div>
   );
 };
 
 TagFilter.displayName = 'TagFilter';
 
-const getStyles = stylesFactory((theme: GrafanaTheme) => {
-  return {
-    tagFilter: css`
-      position: relative;
-      min-width: 180px;
-      flex-grow: 1;
+const getStyles = (theme: GrafanaTheme2) => ({
+  tagFilter: css`
+    position: relative;
+    min-width: 180px;
+    flex-grow: 1;
 
-      .label-tag {
-        margin-left: 6px;
-        cursor: pointer;
-      }
-    `,
-    clear: css`
-      text-decoration: underline;
-      font-size: 12px;
-      position: absolute;
-      top: -22px;
-      right: 0;
+    .label-tag {
+      margin-left: 6px;
       cursor: pointer;
-      color: ${theme.colors.textWeak};
+    }
+  `,
+  clear: css`
+    text-decoration: underline;
+    font-size: 12px;
+    position: absolute;
+    top: -22px;
+    right: 0;
+    cursor: pointer;
+    color: ${theme.colors.text.secondary};
 
-      &:hover {
-        color: ${theme.colors.textStrong};
-      }
-    `,
-  };
+    &:hover {
+      color: ${theme.colors.text.primary};
+    }
+  `,
 });
diff --git a/public/app/features/playlist/PlaylistEditPage.test.tsx b/public/app/features/playlist/PlaylistEditPage.test.tsx
index 0d8941313fb..2766c0fe6c4 100644
--- a/public/app/features/playlist/PlaylistEditPage.test.tsx
+++ b/public/app/features/playlist/PlaylistEditPage.test.tsx
@@ -12,6 +12,12 @@ jest.mock('@grafana/runtime', () => ({
   getBackendSrv: () => backendSrv,
 }));
 
+jest.mock('../../core/components/TagFilter/TagFilter', () => ({
+  TagFilter: () => {
+    return <>mocked-tag-filter</>;
+  },
+}));
+
 async function getTestContext({ name, interval, items }: Partial<Playlist> = {}) {
   jest.clearAllMocks();
   const playlist = { name, items, interval } as unknown as Playlist;
diff --git a/public/app/features/playlist/PlaylistForm.test.tsx b/public/app/features/playlist/PlaylistForm.test.tsx
index 8a5a774579e..b401838b698 100644
--- a/public/app/features/playlist/PlaylistForm.test.tsx
+++ b/public/app/features/playlist/PlaylistForm.test.tsx
@@ -6,6 +6,12 @@ import userEvent from '@testing-library/user-event';
 import { Playlist } from './types';
 import { PlaylistForm } from './PlaylistForm';
 
+jest.mock('../../core/components/TagFilter/TagFilter', () => ({
+  TagFilter: () => {
+    return <>mocked-tag-filter</>;
+  },
+}));
+
 function getTestContext({ name, interval, items }: Partial<Playlist> = {}) {
   const onSubmitMock = jest.fn();
   const playlist = { name, items, interval } as unknown as Playlist;
diff --git a/public/app/features/playlist/PlaylistNewPage.test.tsx b/public/app/features/playlist/PlaylistNewPage.test.tsx
index f3469adba79..487bd152607 100644
--- a/public/app/features/playlist/PlaylistNewPage.test.tsx
+++ b/public/app/features/playlist/PlaylistNewPage.test.tsx
@@ -19,6 +19,12 @@ jest.mock('@grafana/runtime', () => ({
   getBackendSrv: () => backendSrv,
 }));
 
+jest.mock('../../core/components/TagFilter/TagFilter', () => ({
+  TagFilter: () => {
+    return <>mocked-tag-filter</>;
+  },
+}));
+
 function getTestContext({ name, interval, items }: Partial<Playlist> = {}) {
   jest.clearAllMocks();
   const playlist = { name, items, interval } as unknown as Playlist;
diff --git a/public/app/features/search/components/DashboardSearch.test.tsx b/public/app/features/search/components/DashboardSearch.test.tsx
index 029ab069a51..8004154fc21 100644
--- a/public/app/features/search/components/DashboardSearch.test.tsx
+++ b/public/app/features/search/components/DashboardSearch.test.tsx
@@ -112,10 +112,11 @@ describe('DashboardSearch', () => {
     await waitFor(() => screen.getByLabelText('Tag filter'));
 
     const tagComponent = screen.getByLabelText('Tag filter');
-    await selectOptionInTest(tagComponent, 'tag1');
-
     expect(tagComponent).toBeInTheDocument();
 
+    tagComponent.focus();
+    await waitFor(() => selectOptionInTest(tagComponent, 'tag1'));
+
     await waitFor(() =>
       expect(mockSearch).toHaveBeenCalledWith({
         query: '',
diff --git a/public/app/features/search/components/SearchResultsFilter.test.tsx b/public/app/features/search/components/SearchResultsFilter.test.tsx
index ff587ac1ec7..e88ce063da2 100644
--- a/public/app/features/search/components/SearchResultsFilter.test.tsx
+++ b/public/app/features/search/components/SearchResultsFilter.test.tsx
@@ -83,6 +83,7 @@ describe('SearchResultsFilter', () => {
       query: { ...searchQuery, tag: [] },
     });
     const tagComponent = await screen.findByLabelText('Tag filter');
+    await tagComponent.focus();
     await selectOptionInTest(tagComponent, 'tag1');
 
     expect(mockFilterByTags).toHaveBeenCalledTimes(1);
diff --git a/public/app/features/search/page/SearchPage.tsx b/public/app/features/search/page/SearchPage.tsx
index a9e8ced3802..923f677bc43 100644
--- a/public/app/features/search/page/SearchPage.tsx
+++ b/public/app/features/search/page/SearchPage.tsx
@@ -63,7 +63,8 @@ export default function SearchPage() {
         {results.loading && <Spinner />}
         {results.value?.body && (
           <div>
-            <TagFilter isClearable tags={query.tag} tagOptions={getTagOptions} onChange={onTagChange} /> <br />
+            <TagFilter isClearable tags={query.tag} tagOptions={getTagOptions} onChange={onTagChange} />
+            <br />
             {query.datasource && (
               <Button
                 icon="times"