diff --git a/modules/markup/html.go b/modules/markup/html.go
index 11888b8536..da16bcd3cb 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -630,7 +630,7 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
 		}
 		mentionedUsername := mention[1:]
 
-		if processorHelper.IsUsernameMentionable != nil && processorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
+		if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
 			replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mentionedUsername), mention, "mention"))
 			node = node.NextSibling.NextSibling
 		} else {
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
index 816e93b700..f03a780900 100644
--- a/modules/markup/markdown/goldmark.go
+++ b/modules/markup/markdown/goldmark.go
@@ -47,6 +47,12 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 		tocMode = rc.TOC
 	}
 
+	applyElementDir := func(n ast.Node) {
+		if markup.DefaultProcessorHelper.ElementDir != "" {
+			n.SetAttributeString("dir", []byte(markup.DefaultProcessorHelper.ElementDir))
+		}
+	}
+
 	attentionMarkedBlockquotes := make(container.Set[*ast.Blockquote])
 	_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
 		if !entering {
@@ -69,6 +75,9 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 				header.ID = util.BytesToReadOnlyString(id.([]byte))
 			}
 			tocList = append(tocList, header)
+			applyElementDir(v)
+		case *ast.Paragraph:
+			applyElementDir(v)
 		case *ast.Image:
 			// Images need two things:
 			//
@@ -171,6 +180,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 					v.AppendChild(v, newChild)
 				}
 			}
+			applyElementDir(v)
 		case *ast.Text:
 			if v.SoftLineBreak() && !v.HardLineBreak() {
 				renderMetas := pc.Get(renderMetasKey).(map[string]string)
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index f2477f1e9e..0331c3742a 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -30,14 +30,16 @@ const (
 
 type ProcessorHelper struct {
 	IsUsernameMentionable func(ctx context.Context, username string) bool
+
+	ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
 }
 
-var processorHelper ProcessorHelper
+var DefaultProcessorHelper ProcessorHelper
 
 // Init initialize regexps for markdown parsing
 func Init(ph *ProcessorHelper) {
 	if ph != nil {
-		processorHelper = *ph
+		DefaultProcessorHelper = *ph
 	}
 
 	NewSanitizer()
diff --git a/services/markup/processorhelper.go b/services/markup/processorhelper.go
index 2897f203a9..3551f85c46 100644
--- a/services/markup/processorhelper.go
+++ b/services/markup/processorhelper.go
@@ -13,6 +13,7 @@ import (
 
 func ProcessorHelper() *markup.ProcessorHelper {
 	return &markup.ProcessorHelper{
+		ElementDir: "auto", // set dir="auto" for necessary (eg: <p>, <h?>, etc) tags
 		IsUsernameMentionable: func(ctx context.Context, username string) bool {
 			mentionedUser, err := user.GetUserByName(ctx, username)
 			if err != nil {
diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go
index fa8e6e85c7..65cba1dee3 100644
--- a/tests/integration/user_test.go
+++ b/tests/integration/user_test.go
@@ -250,7 +250,7 @@ func TestGetUserRss(t *testing.T) {
 		title, _ := rssDoc.ChildrenFiltered("title").Html()
 		assert.EqualValues(t, "Feed of &#34;the_1-user.with.all.allowedChars&#34;", title)
 		description, _ := rssDoc.ChildrenFiltered("description").Html()
-		assert.EqualValues(t, "&lt;p&gt;some &lt;a href=&#34;https://commonmark.org/&#34; rel=&#34;nofollow&#34;&gt;commonmark&lt;/a&gt;!&lt;/p&gt;\n", description)
+		assert.EqualValues(t, "&lt;p dir=&#34;auto&#34;&gt;some &lt;a href=&#34;https://commonmark.org/&#34; rel=&#34;nofollow&#34;&gt;commonmark&lt;/a&gt;!&lt;/p&gt;\n", description)
 	}
 }
 
diff --git a/web_src/css/base.css b/web_src/css/base.css
index 36624ab957..6c6c5381ad 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -1091,6 +1091,7 @@ a.label,
   color: var(--color-text);
   background: var(--color-box-body);
   border-color: var(--color-secondary);
+  text-align: start; /* Override fomantic's `text-align: left` to make RTL work via HTML `dir="auto"` */
 }
 
 .ui.table th,
diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css
index db67ac4263..5b2d6ef244 100644
--- a/web_src/css/markup/content.css
+++ b/web_src/css/markup/content.css
@@ -23,9 +23,9 @@
 }
 
 .markup .anchor {
+  float: left;
   padding-right: 4px;
   margin-left: -20px;
-  line-height: 1;
   color: inherit;
 }
 
@@ -37,6 +37,10 @@
   outline: none;
 }
 
+.markup h1 .anchor {
+  margin-top: -2px; /* re-align to center */
+}
+
 .markup h1 .anchor .svg,
 .markup h2 .anchor .svg,
 .markup h3 .anchor .svg,