diff --git a/app/javascript/mastodon/components/hashtag_bar.jsx b/app/javascript/mastodon/components/hashtag_bar.jsx
new file mode 100644
index 000000000..6a39005e1
--- /dev/null
+++ b/app/javascript/mastodon/components/hashtag_bar.jsx
@@ -0,0 +1,50 @@
+import PropTypes from 'prop-types';
+import { useMemo, useState, useCallback } from 'react';
+
+import { FormattedMessage } from 'react-intl';
+
+import { Link } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+
+const domParser = new DOMParser();
+
+// About two lines on desktop
+const VISIBLE_HASHTAGS = 7;
+
+export const HashtagBar = ({ hashtags, text }) => {
+  const renderedHashtags = useMemo(() => {
+    const body = domParser.parseFromString(text, 'text/html').documentElement;
+    return [].map.call(body.querySelectorAll('[rel=tag]'), node => node.textContent.toLowerCase());
+  }, [text]);
+
+  const invisibleHashtags = useMemo(() => (
+    hashtags.filter(hashtag => !renderedHashtags.some(textContent => textContent === `#${hashtag.get('name')}` || textContent === hashtag.get('name')))
+  ), [hashtags, renderedHashtags]);
+
+  const [expanded, setExpanded] = useState(false);
+  const handleClick = useCallback(() => setExpanded(true), []);
+
+  if (invisibleHashtags.isEmpty()) {
+    return null;
+  }
+
+  const revealedHashtags = expanded ? invisibleHashtags : invisibleHashtags.take(VISIBLE_HASHTAGS);
+
+  return (
+    <div className='hashtag-bar'>
+      {revealedHashtags.map(hashtag => (
+        <Link key={hashtag.get('name')} to={`/tags/${hashtag.get('name')}`}>
+          #{hashtag.get('name')}
+        </Link>
+      ))}
+
+      {!expanded && invisibleHashtags.size > VISIBLE_HASHTAGS && <button className='link-button' onClick={handleClick}><FormattedMessage id='hashtags.and_other' defaultMessage='…and {count, plural, other {# more}}' values={{ count: invisibleHashtags.size - VISIBLE_HASHTAGS }} /></button>}
+    </div>
+  );
+};
+
+HashtagBar.propTypes = {
+  hashtags: ImmutablePropTypes.list,
+  text: PropTypes.string,
+};
\ No newline at end of file
diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx
index 37951d578..7c34684d7 100644
--- a/app/javascript/mastodon/components/status.jsx
+++ b/app/javascript/mastodon/components/status.jsx
@@ -22,6 +22,7 @@ import { displayMedia } from '../initial_state';
 import { Avatar } from './avatar';
 import { AvatarOverlay } from './avatar_overlay';
 import { DisplayName } from './display_name';
+import { HashtagBar } from './hashtag_bar';
 import { RelativeTimestamp } from './relative_timestamp';
 import StatusActionBar from './status_action_bar';
 import StatusContent from './status_content';
@@ -580,6 +581,8 @@ class Status extends ImmutablePureComponent {
 
             {media}
 
+            <HashtagBar hashtags={status.get('tags')} text={status.get('content')} />
+
             <StatusActionBar scrollKey={scrollKey} status={status} account={account} onFilter={matchedFilters ? this.handleFilterClick : null} {...other} />
           </div>
         </div>
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx
index 7348905c5..c1815b916 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.jsx
+++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx
@@ -10,6 +10,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 
 import { AnimatedNumber } from 'mastodon/components/animated_number';
 import EditedTimestamp from 'mastodon/components/edited_timestamp';
+import { HashtagBar } from 'mastodon/components/hashtag_bar';
 import { Icon }  from 'mastodon/components/icon';
 import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
 
@@ -314,6 +315,8 @@ class DetailedStatus extends ImmutablePureComponent {
 
           {media}
 
+          <HashtagBar hashtags={status.get('tags')} text={status.get('content')} />
+
           <div className='detailed-status__meta'>
             <a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} target='_blank' rel='noopener noreferrer'>
               <FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 5b7b4f6b3..7205bff05 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -300,6 +300,7 @@
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} post} other {{counter} posts}} today",
   "hashtag.follow": "Follow hashtag",
   "hashtag.unfollow": "Unfollow hashtag",
+  "hashtags.and_other": "…and {count, plural, other {# more}}",
   "home.actions.go_to_explore": "See what's trending",
   "home.actions.go_to_suggestions": "Find people to follow",
   "home.column_settings.basic": "Basic",
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 46c7d328c..009d4f71d 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -9256,3 +9256,26 @@ noscript {
     }
   }
 }
+
+.hashtag-bar {
+  margin-top: 16px;
+  display: flex;
+  flex-wrap: wrap;
+  font-size: 14px;
+  gap: 4px;
+
+  a {
+    display: inline-flex;
+    border-radius: 4px;
+    background: rgba($highlight-text-color, 0.2);
+    color: $highlight-text-color;
+    padding: 0.4em 0.6em;
+    text-decoration: none;
+
+    &:hover,
+    &:focus,
+    &:active {
+      background: rgba($highlight-text-color, 0.3);
+    }
+  }
+}