Asking for feedback: Tag Reveal Component in Topic Lists - Expand/Collapse tags in topic lists

Note: Before I post this in theme components, I wanted to get some feedback first if this theme component qualifies or if there are any major issues with it.

:warning: Disclosure: This theme component was planned, implemented, and tested with the help of AI coding tools.

Would love to hear your feedback!


:information_source: Summary Tag Reveal
:eyeglasses: Preview Not available…
:hammer_and_wrench: Repository GitHub - jrgong420/discourse-tag-reveal
:question: Install Guide How to install a theme or theme component
:open_book: New to Discourse Themes? Beginner’s guide to using Discourse Themes

Discourse Tag Reveal is a lightweight theme component that keeps topic lists tidy by showing only the first N tags per topic and replacing the rest with an accessible “+X more tags” toggle. Users can expand to see all tags and collapse back to the shortened view. It works out of the box with Discourse’s standard tag UI and requires no server-side changes.

Features

  • Configurable tag limit (default: 5) via theme settings

  • Toggle styled as a tag, keyboard accessible (Enter/Space) with ARIA attributes

  • Localized strings using themePrefix and discourse-i18n

  • SPA-safe behavior: resets and re-applies logic on page changes

  • Supports infinite scrolling via MutationObserver

  • Minimal CSS; respects core tag styles

  • No template overrides or plugin dependencies

Video Demo:

CleanShot 2025-10-18 at 19.28.39

Installation & Configuration

  • Tested with Discourse version: 3.6.0beta1

  • Configure settings under the component’s Settings tab:

  • max_tags_visible (integer, default 5): How many tags to show before collapsing

  • toggle_tag_style : Visual style of the toggle to match tag appearance (Currently only “box” style implemented)

  • Scope: affects topic lists (Latest, New, Unread, and category topic lists)

Compatibility with other Theme Components

:warning: Only minimal tests performed, please test yourself before deploying to production

Notes

  • Ensure tagging is enabled (Admin → Settings → Tags), otherwise you won’t see any effect

  • If your site heavily customizes tag CSS, you may want to tweak .ts-toggle styles for perfect visual alignment

Ideas for the future

I don’t really plan to implement more features but I’m happy to accept PRs. Some ideas for the future:

  • Enable/disable for tags in topic view

  • Granular control for specific pages and/or categories

2 Likes

Did you deliberately name the setting the same as a setting in the core? I would be concerned about misunderstandings.

1 Like

good catch! Just modified it…

2 Likes

Looks really interesting. I’ll try it on my dev env larer since it doesn’t seem to work on Theme Creator (unless I’m doing something wrong?) :thinking:.

Sounds interesting! Could you share a few screenshots or screen recordings of the feature in action?

:smiley: Added a quick video demo in the first post, see here:

I haven’t even checked how to submit/add my TC component there…:smiley:
But either way, I prefer to gather some feedback here first, and once it’s ready to be published in Theme component, I will see how to add it there.

3 Likes

Theme creator doesn’t use the box style for tags

You may want to use

more_tags:
  one: "+%{count} more tag"
  other: "+%{count} more tags"
1 Like

good point. I forgot to change the default label to +%{count} more to keep it short and concise, that’s how we use it and keep things compact and clean.

1 Like

Hey,

This feature could be interesting in some situations!

At first glance, there are a few things to note:

  • Theme settings and Site settings are not the same. You need to retrieve the service first to access max_tags_per_topic, e.g.: const siteSettings = api.container.lookup("service:site-settings");

  • The extra checks to get the limit should not be necessary; you can retrieve the value directly. You can probably do Math.min(settings.max_tags_visible, siteSettings.max_tags_per_topic )

  • You are not restoring the visibility of separators.

  • You might want to unregister the events

  • The process on initial load should not be needed with MutationObserver. Usually, before going global, you would want to check first if there is a way to reduce the scope around the element using the API (plugin outlet, for example).

Let me check if there is a different way!

1 Like

Since it’s in the api-initializers file, would @service siteSettings also work?

Can you check now? The latest commit should have fixed the addressed points

The minimum Discourse version 3.6.0 means it will take quite a while until anyone will be able to use it. Did you mean 3.5.0 or 3.6.0beta1?

I meant 3.6.0beta1, that’s the version I have been testing it with…

You use this in a class. It won’t work otherwise.

You want to write 3.6.0.beta1 then, otherwise no one can install it at the moment.

I did check a bit. Indeed, there is no straightforward way to achieve that; however, I found an interesting and simplified method to do it using the API.

  • It uses the topic model to change what visible tags will be output before the template is generated. It means no DOM manipulation and settings agnostic. Depending on the state (revealTags), it will return the original list or a partial one.

  • To create the toggle button, it uses the API to add a tag with the HTML of a button (unfortunately, there is no plugin outlet here). The click event is handled separately. On click, the toggle state is updated (revealTags), and we trigger a re-render of the tags list.

The big advantage of this way is that you don’t have to mess with the HTML and figure out what to show/unhide with CSS, based on the different styles.

chrome_lSKqwYt5Z7

Sharing my test code here:

import { apiInitializer } from "discourse/lib/api";
import { i18n } from "discourse-i18n";
import { computed } from "@ember/object";

export default apiInitializer((api) => {
  const siteSettings = api.container.lookup("service:site-settings");

  const maxVisibleTags = Math.min(
    settings.max_tags_visible,
    siteSettings.max_tags_per_topic
  );

  let topicModels = {};

  api.modifyClass(
    "model:topic",
    (Superclass) =>
      class extends Superclass {
        revealTags = false;

        init() {
          super.init(...arguments);
          topicModels[this.id] = this;
        }

        @computed("tags")
        get visibleListTags() {
          if (this.revealTags) {
            return super.visibleListTags;
          }
          return super.visibleListTags.slice(0, maxVisibleTags);
        }
      }
  );

  api.addTagsHtmlCallback(
    (topic, params) => {
      if (topic.tags.length <= maxVisibleTags) {
        return "";
      }

      const isExpanded = topic.revealTags;
      const label = isExpanded
        ? i18n(themePrefix("js.tag_reveal.hide"))
        : i18n(themePrefix("js.tag_reveal.more_tags"), {
            count: topic.tags.length - maxVisibleTags,
          });

      return `<a class="reveal-tag-action" role="button" aria-expanded="${isExpanded}">${label}</a>`;
    },
    {
      priority: siteSettings.max_tags_per_topic + 1,
    }
  );

  document.addEventListener("click", (event) => {
    const target = event.target;
    if (!target?.matches(".reveal-tag-action")) {
      return;
    }

    event.preventDefault();
    event.stopPropagation();

    const element =
      target.closest("[data-topic-id]") ||
      document.querySelector("h1[data-topic-id]");
    const topicId = element?.dataset.topicId;
    if (!topicId) {
      return;
    }

    const topicModel = topicModels[topicId];
    if (!topicModel) {
      return;
    }

    topicModel.revealTags = !topicModel.revealTags;
    topicModel.notifyPropertyChange("tags");
  });
});
.reveal-tag-action {
  background-color: var(--primary-50);
  border: 1px solid var(--primary-200);
  color: var(--primary-800);
  font-size: small;
  padding-inline: 3px;
}

.discourse-tags__tag-separator:has(+ .reveal-tag-action) {
  visibility: hidden;
}

2 Likes

Hey guys I pushed another update and added additional experimental features (“featured” tags which always come first and not calculated towards max amount + Highlight topic row in topic list view) so the overall TC is pivoting a bit with more extended functionality to highlight certain based on configured tabs

@Arkshine thanks for sharing your simplified method, I really appreciate it!!!

It also affected single topic view, so we added a setting to enable that behavior manually. Also with the new method the expanded state persists when navigating to different route/page, but I haven’t addressed that yet.

I implemented it into this branch: