diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
index 5dff9c637..ca47583d0 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
@@ -20,9 +20,7 @@ import eu.kanade.tachiyomi.ui.base.controller.getMainAppBarHeight
 import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.util.system.copyToClipboard
 import eu.kanade.tachiyomi.util.view.loadAnyAutoPause
-import eu.kanade.tachiyomi.util.view.setChips
 import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
 import reactivecircus.flowbinding.android.view.clicks
 import reactivecircus.flowbinding.android.view.longClicks
@@ -45,13 +43,14 @@ class MangaInfoHeaderAdapter(
 
     private lateinit var binding: MangaInfoHeaderBinding
 
-    private var initialLoad: Boolean = true
-
-    private val maxLines = 3
-
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
         binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
         updateCoverPosition()
+
+        // Expand manga info if navigated from source listing or explicitly set to
+        // (e.g. on tablets)
+        binding.mangaSummarySection.expanded = fromSource || isTablet
+
         return HeaderViewHolder(binding.root)
     }
 
@@ -180,15 +179,6 @@ class MangaInfoHeaderAdapter(
                 }
                 .launchIn(controller.viewScope)
 
-            binding.mangaSummaryText.longClicks()
-                .onEach {
-                    controller.activity?.copyToClipboard(
-                        view.context.getString(R.string.description),
-                        binding.mangaSummaryText.text.toString()
-                    )
-                }
-                .launchIn(controller.viewScope)
-
             binding.mangaCover.clicks()
                 .onEach {
                     controller.showFullCoverDialog()
@@ -201,7 +191,7 @@ class MangaInfoHeaderAdapter(
                 }
                 .launchIn(controller.viewScope)
 
-            setMangaInfo(manga, source)
+            setMangaInfo()
         }
 
         private fun showCoverOptionsDialog() {
@@ -231,7 +221,7 @@ class MangaInfoHeaderAdapter(
          * @param manga manga object containing information about manga.
          * @param source the source of the manga.
          */
-        private fun setMangaInfo(manga: Manga, source: Source?) {
+        private fun setMangaInfo() {
             // Update full title TextView.
             binding.mangaFullTitle.text = if (manga.title.isBlank()) {
                 view.context.getString(R.string.unknown)
@@ -254,27 +244,23 @@ class MangaInfoHeaderAdapter(
             }
 
             // If manga source is known update source TextView.
-            val mangaSource = source?.toString()
+            val mangaSource = source.toString()
             with(binding.mangaSource) {
-                if (mangaSource != null) {
-                    val enabledLanguages = preferences.enabledLanguages().get()
-                        .filterNot { it in listOf("all", "other") }
+                val enabledLanguages = preferences.enabledLanguages().get()
+                    .filterNot { it in listOf("all", "other") }
 
-                    val hasOneActiveLanguages = enabledLanguages.size == 1
-                    val isInEnabledLanguages = source.lang in enabledLanguages
-                    text = when {
-                        // For edge cases where user disables a source they got manga of in their library.
-                        hasOneActiveLanguages && !isInEnabledLanguages -> mangaSource
-                        // Hide the language tag when only one language is used.
-                        hasOneActiveLanguages && isInEnabledLanguages -> source.name
-                        else -> mangaSource
-                    }
+                val hasOneActiveLanguages = enabledLanguages.size == 1
+                val isInEnabledLanguages = source.lang in enabledLanguages
+                text = when {
+                    // For edge cases where user disables a source they got manga of in their library.
+                    hasOneActiveLanguages && !isInEnabledLanguages -> mangaSource
+                    // Hide the language tag when only one language is used.
+                    hasOneActiveLanguages && isInEnabledLanguages -> source.name
+                    else -> mangaSource
+                }
 
-                    setOnClickListener {
-                        controller.performSearch(sourceManager.getOrStub(source.id).name)
-                    }
-                } else {
-                    text = view.context.getString(R.string.unknown)
+                setOnClickListener {
+                    controller.performSearch(sourceManager.getOrStub(source.id).name)
                 }
             }
 
@@ -296,84 +282,9 @@ class MangaInfoHeaderAdapter(
             binding.mangaCover.loadAnyAutoPause(manga)
 
             // Manga info section
-            val hasInfoContent = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()
-            showMangaInfo(hasInfoContent)
-            if (hasInfoContent) {
-                // Update description TextView.
-                binding.mangaSummaryText.text = updateDescription(manga.description, (fromSource || isTablet).not())
-
-                // Update genres list
-                if (!manga.genre.isNullOrBlank()) {
-                    binding.mangaGenresTagsCompactChips.setChips(
-                        manga.getGenres(),
-                        controller::performGenreSearch
-                    )
-                    binding.mangaGenresTagsFullChips.setChips(
-                        manga.getGenres(),
-                        controller::performGenreSearch
-                    )
-                } else {
-                    binding.mangaGenresTagsCompact.isVisible = false
-                    binding.mangaGenresTagsCompactChips.isVisible = false
-                    binding.mangaGenresTagsFullChips.isVisible = false
-                }
-
-                // Handle showing more or less info
-                merge(
-                    binding.mangaSummaryText.clicks(),
-                    binding.mangaInfoToggleMore.clicks(),
-                    binding.mangaInfoToggleLess.clicks(),
-                    binding.mangaSummarySection.clicks(),
-                )
-                    .onEach { toggleMangaInfo() }
-                    .launchIn(controller.viewScope)
-
-                if (initialLoad) {
-                    binding.mangaGenresTagsCompact.requestLayout()
-                }
-
-                // Expand manga info if navigated from source listing or explicitly set to
-                // (e.g. on tablets)
-                if (initialLoad && (fromSource || isTablet)) {
-                    toggleMangaInfo()
-                    initialLoad = false
-                }
-            }
-        }
-
-        private fun showMangaInfo(visible: Boolean) {
-            binding.mangaSummarySection.isVisible = visible
-        }
-
-        private fun toggleMangaInfo() {
-            val isCurrentlyExpanded = binding.mangaSummaryText.maxLines != maxLines
-
-            binding.mangaInfoToggleMore.isVisible = isCurrentlyExpanded
-            binding.mangaInfoScrim.isVisible = isCurrentlyExpanded
-            binding.mangaInfoToggleMoreScrim.isVisible = isCurrentlyExpanded
-            binding.mangaGenresTagsCompact.isVisible = isCurrentlyExpanded
-            binding.mangaGenresTagsCompactChips.isVisible = isCurrentlyExpanded
-
-            binding.mangaInfoToggleLess.isVisible = !isCurrentlyExpanded
-            binding.mangaGenresTagsFullChips.isVisible = !isCurrentlyExpanded
-
-            binding.mangaSummaryText.text = updateDescription(manga.description, isCurrentlyExpanded)
-
-            binding.mangaSummaryText.maxLines = when {
-                isCurrentlyExpanded -> maxLines
-                else -> Int.MAX_VALUE
-            }
-        }
-
-        private fun updateDescription(description: String?, isCurrentlyExpanded: Boolean): CharSequence {
-            return when {
-                description.isNullOrBlank() -> view.context.getString(R.string.unknown)
-                isCurrentlyExpanded ->
-                    description
-                        .replace(Regex(" +\$", setOf(RegexOption.MULTILINE)), "")
-                        .replace(Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE)), "\n")
-                else -> description
-            }
+            binding.mangaSummarySection.isVisible = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()
+            binding.mangaSummarySection.description = manga.description
+            binding.mangaSummarySection.setTags(manga.getGenres(), controller::performGenreSearch)
         }
 
         /**
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/MangaSummaryView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/MangaSummaryView.kt
new file mode 100644
index 000000000..d16b814d5
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/widget/MangaSummaryView.kt
@@ -0,0 +1,188 @@
+package eu.kanade.tachiyomi.widget
+
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import androidx.annotation.AttrRes
+import androidx.annotation.StyleRes
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
+import androidx.core.view.doOnNextLayout
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.databinding.MangaSummaryBinding
+import eu.kanade.tachiyomi.util.system.animatorDurationScale
+import eu.kanade.tachiyomi.util.system.copyToClipboard
+import eu.kanade.tachiyomi.util.view.setChips
+import kotlin.math.roundToInt
+import kotlin.math.roundToLong
+
+class MangaSummaryView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    @AttrRes defStyleAttr: Int = 0,
+    @StyleRes defStyleRes: Int = 0
+) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
+
+    private val binding = MangaSummaryBinding.inflate(LayoutInflater.from(context), this, true)
+
+    private var animatorSet: AnimatorSet? = null
+
+    private var recalculateHeights = false
+    private var descExpandedHeight = -1
+    private var descShrunkHeight = -1
+
+    var expanded = false
+        set(value) {
+            if (field != value) {
+                field = value
+                updateExpandState()
+            }
+        }
+
+    var description: CharSequence? = null
+        set(value) {
+            if (field != value) {
+                field = if (value.isNullOrBlank()) {
+                    context.getString(R.string.unknown)
+                } else {
+                    value
+                }
+                binding.descriptionText.text = field
+                recalculateHeights = true
+                doOnNextLayout {
+                    updateExpandState()
+                }
+                requestLayout()
+            }
+        }
+
+    fun setTags(items: List<String>?, onClick: (item: String) -> Unit) {
+        binding.tagChipsShrunk.setChips(items, onClick)
+        binding.tagChipsExpanded.setChips(items, onClick)
+    }
+
+    private fun updateExpandState() = binding.apply {
+        val initialSetup = descriptionText.maxHeight < 0
+
+        val maxHeightTarget = if (expanded) descExpandedHeight else descShrunkHeight
+        val maxHeightStart = if (initialSetup) maxHeightTarget else descriptionText.maxHeight
+        val descMaxHeightAnimator = ValueAnimator().apply {
+            setIntValues(maxHeightStart, maxHeightTarget)
+            addUpdateListener {
+                descriptionText.maxHeight = it.animatedValue as Int
+            }
+        }
+
+        val toggleDrawable = ContextCompat.getDrawable(
+            context,
+            if (expanded) R.drawable.anim_caret_up else R.drawable.anim_caret_down
+        )
+        toggleMore.setImageDrawable(toggleDrawable)
+
+        var pastHalf = false
+        val toggleTarget = if (expanded) 1F else 0F
+        val toggleStart = if (initialSetup) {
+            toggleTarget
+        } else {
+            toggleMore.translationY / toggleMore.height
+        }
+        val toggleAnimator = ValueAnimator().apply {
+            setFloatValues(toggleStart, toggleTarget)
+            addUpdateListener {
+                val value = it.animatedValue as Float
+
+                toggleMore.translationY = toggleMore.height * value
+                descriptionScrim.translationY = toggleMore.translationY
+                toggleMoreScrim.translationY = toggleMore.translationY
+                tagChipsShrunkContainer.updateLayoutParams<ConstraintLayout.LayoutParams> {
+                    topMargin = toggleMore.translationY.roundToInt()
+                }
+                tagChipsExpanded.updateLayoutParams<ConstraintLayout.LayoutParams> {
+                    topMargin = toggleMore.translationY.roundToInt()
+                }
+
+                // Update non-animatable objects mid-animation makes it feel less abrupt
+                if (it.animatedFraction >= 0.5F && !pastHalf) {
+                    pastHalf = true
+                    descriptionText.text = trimWhenNeeded(description)
+                    tagChipsShrunkContainer.scrollX = 0
+                    tagChipsShrunkContainer.isVisible = !expanded
+                    tagChipsExpanded.isVisible = expanded
+                }
+            }
+        }
+
+        animatorSet?.cancel()
+        animatorSet = AnimatorSet().apply {
+            interpolator = FastOutSlowInInterpolator()
+            duration = (TOGGLE_ANIM_DURATION * context.animatorDurationScale).roundToLong()
+            playTogether(toggleAnimator, descMaxHeightAnimator)
+            start()
+        }
+        (toggleDrawable as? Animatable)?.start()
+    }
+
+    private fun trimWhenNeeded(text: CharSequence?): CharSequence? {
+        return if (!expanded) {
+            text
+                ?.replace(Regex(" +\$", setOf(RegexOption.MULTILINE)), "")
+                ?.replace(Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE)), "\n")
+        } else {
+            text
+        }
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        if (!recalculateHeights) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+            return
+        }
+        recalculateHeights = false
+
+        // Measure with expanded lines
+        binding.descriptionText.maxLines = Int.MAX_VALUE
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        descExpandedHeight = binding.descriptionText.measuredHeight
+
+        // Measure with shrunk lines
+        binding.descriptionText.maxLines = SHRUNK_DESC_MAX_LINES
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        descShrunkHeight = binding.descriptionText.measuredHeight
+    }
+
+    init {
+        binding.descriptionText.apply {
+            // So that 1 line of text won't be hidden by scrim
+            minLines = DESC_MIN_LINES
+
+            setOnLongClickListener {
+                context.copyToClipboard(
+                    context.getString(R.string.description),
+                    text.toString()
+                )
+                true
+            }
+        }
+
+        arrayOf(
+            binding.descriptionText,
+            binding.descriptionScrim,
+            binding.toggleMoreScrim,
+            binding.toggleMore
+        ).forEach {
+            it.setOnClickListener { expanded = !expanded }
+        }
+    }
+}
+
+private const val TOGGLE_ANIM_DURATION = 300L
+
+private const val DESC_MIN_LINES = 2
+private const val SHRUNK_DESC_MAX_LINES = 3
diff --git a/app/src/main/res/drawable/anim_caret_down.xml b/app/src/main/res/drawable/anim_caret_down.xml
new file mode 100644
index 000000000..e5288406e
--- /dev/null
+++ b/app/src/main/res/drawable/anim_caret_down.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:name="caret_up"
+            android:width="24.0dip"
+            android:height="24.0dip"
+            android:viewportWidth="24.0"
+            android:viewportHeight="24.0">
+            <group
+                android:name="caret01"
+                android:rotation="90.0"
+                android:translateX="12.0"
+                android:translateY="15.0">
+                <group
+                    android:name="caret_l"
+                    android:rotation="45.0">
+                    <group
+                        android:name="caret_l_pivot"
+                        android:translateY="4.0">
+                        <group
+                            android:name="caret_l_rect_position"
+                            android:translateY="-1.0">
+                            <path
+                                android:name="caret_l_rect"
+                                android:fillColor="@android:color/black"
+                                android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
+                        </group>
+                    </group>
+                </group>
+                <group
+                    android:name="caret_r"
+                    android:rotation="-45.0">
+                    <group
+                        android:name="caret_r_pivot"
+                        android:translateY="-4.0">
+                        <group
+                            android:name="caret_r_rect_position"
+                            android:translateY="1.0">
+                            <path
+                                android:name="caret_r_rect"
+                                android:fillColor="@android:color/black"
+                                android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
+                        </group>
+                    </group>
+                </group>
+            </group>
+        </vector>
+    </aapt:attr>
+
+    <target android:name="caret01">
+        <aapt:attr name="android:animation">
+            <objectAnimator
+                android:duration="300"
+                android:interpolator="@android:interpolator/fast_out_slow_in"
+                android:pathData="M 12.0,9.0 c 0.0,0.66667 0.0,5.0 0.0,6.0"
+                android:propertyXName="translateX"
+                android:propertyYName="translateY" />
+        </aapt:attr>
+    </target>
+    <target android:name="caret_l">
+        <aapt:attr name="android:animation">
+            <objectAnimator
+                android:duration="300"
+                android:interpolator="@android:interpolator/fast_out_slow_in"
+                android:propertyName="rotation"
+                android:valueFrom="-45.0"
+                android:valueTo="45.0"
+                android:valueType="floatType" />
+        </aapt:attr>
+    </target>
+    <target
+        android:name="caret_r">
+        <aapt:attr name="android:animation">
+            <objectAnimator
+                android:duration="300"
+                android:interpolator="@android:interpolator/fast_out_slow_in"
+                android:propertyName="rotation"
+                android:valueFrom="45.0"
+                android:valueTo="-45.0"
+                android:valueType="floatType" />
+        </aapt:attr>
+    </target>
+</animated-vector>
diff --git a/app/src/main/res/drawable/anim_caret_up.xml b/app/src/main/res/drawable/anim_caret_up.xml
new file mode 100644
index 000000000..78b817a16
--- /dev/null
+++ b/app/src/main/res/drawable/anim_caret_up.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:name="caret_up"
+            android:height="24.0dip"
+            android:width="24.0dip"
+            android:viewportWidth="24.0"
+            android:viewportHeight="24.0">
+            <group
+                android:name="caret02"
+                android:rotation="90.0"
+                android:translateX="12.0"
+                android:translateY="9.0">
+                <group
+                    android:name="caret02_l"
+                    android:rotation="-45.0">
+                    <group
+                        android:name="caret02_l_pivot"
+                        android:translateY="4.0">
+                        <group
+                            android:name="caret02_l_rect_position"
+                            android:translateY="-1.0">
+                            <path
+                                android:name="caret02_l_rect"
+                                android:fillColor="@android:color/black"
+                                android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
+                        </group>
+                    </group>
+                </group>
+                <group
+                    android:name="caret02_r"
+                    android:rotation="45.0">
+                    <group
+                        android:name="caret02_r_pivot"
+                        android:translateY="-4.0">
+                        <group
+                            android:name="caret02_r_rect_position"
+                            android:translateY="1.0">
+                            <path
+                                android:name="caret02_r_rect"
+                                android:fillColor="@android:color/black"
+                                android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
+                        </group>
+                    </group>
+                </group>
+            </group>
+        </vector>
+    </aapt:attr>
+
+    <target android:name="caret02">
+        <aapt:attr name="android:animation">
+            <objectAnimator
+                android:interpolator="@android:interpolator/fast_out_slow_in"
+                android:duration="300"
+                android:pathData="M 12.0,15.0 c 0.0,-1.0 0.0,-5.33333 0.0,-6.0"
+                android:propertyXName="translateX"
+                android:propertyYName="translateY" />
+        </aapt:attr>
+    </target>
+    <target android:name="caret02_l">
+        <aapt:attr name="android:animation">
+            <objectAnimator
+                android:interpolator="@android:interpolator/fast_out_slow_in"
+                android:duration="300"
+                android:valueFrom="45.0"
+                android:valueTo="-45.0"
+                android:valueType="floatType"
+                android:propertyName="rotation" />
+        </aapt:attr>
+    </target>
+    <target android:name="caret02_r">
+        <aapt:attr name="android:animation">
+            <objectAnimator
+                android:interpolator="@android:interpolator/fast_out_slow_in"
+                android:duration="300"
+                android:valueFrom="-45.0"
+                android:valueTo="45.0"
+                android:valueType="floatType"
+                android:propertyName="rotation" />
+        </aapt:attr>
+    </target>
+</animated-vector>
diff --git a/app/src/main/res/layout-sw720dp/manga_info_header.xml b/app/src/main/res/layout-sw720dp/manga_info_header.xml
index bff6893b1..c76ab43d8 100644
--- a/app/src/main/res/layout-sw720dp/manga_info_header.xml
+++ b/app/src/main/res/layout-sw720dp/manga_info_header.xml
@@ -188,126 +188,12 @@
 
     </LinearLayout>
 
-    <androidx.constraintlayout.widget.ConstraintLayout
+    <eu.kanade.tachiyomi.widget.MangaSummaryView
         android:id="@+id/manga_summary_section"
-        android:layout_width="match_parent"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginTop="8dp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/manga_actions">
-
-        <TextView
-            android:id="@+id/manga_summary_text"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginEnd="16dp"
-            android:clickable="true"
-            android:ellipsize="end"
-            android:focusable="true"
-            android:maxLines="3"
-            android:textAppearance="?attr/textAppearanceBody2"
-            android:textColor="?android:attr/textColorSecondary"
-            android:textIsSelectable="false"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            tools:text="Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content" />
-
-        <View
-            android:id="@+id/manga_info_scrim"
-            android:layout_width="0dp"
-            android:layout_height="32sp"
-            android:background="@drawable/manga_info_gradient"
-            android:backgroundTint="?android:attr/colorBackground"
-            app:layout_constraintBottom_toBottomOf="@+id/manga_summary_text"
-            app:layout_constraintEnd_toEndOf="@+id/manga_summary_text"
-            app:layout_constraintStart_toStartOf="@+id/manga_summary_text" />
-
-        <View
-            android:id="@+id/manga_info_toggle_more_scrim"
-            android:layout_width="36sp"
-            android:layout_height="18sp"
-            android:background="@drawable/manga_info_more_gradient"
-            android:backgroundTint="?android:attr/colorBackground"
-            app:layout_constraintBottom_toBottomOf="@+id/manga_info_toggle_more"
-            app:layout_constraintEnd_toEndOf="@+id/manga_info_toggle_more"
-            app:layout_constraintStart_toStartOf="@+id/manga_info_toggle_more"
-            app:layout_constraintTop_toTopOf="@+id/manga_info_toggle_more" />
-
-        <ImageButton
-            android:id="@+id/manga_info_toggle_more"
-            style="@style/Widget.Tachiyomi.Button.InlineButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="-4dp"
-            android:paddingStart="0dp"
-            android:paddingEnd="0dp"
-            android:background="@android:color/transparent"
-            android:contentDescription="@string/manga_info_expand"
-            android:src="@drawable/ic_expand_more_24dp"
-            app:layout_constraintBottom_toBottomOf="@id/manga_summary_text"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:tint="?android:attr/textColorPrimary" />
-
-        <ImageButton
-            android:id="@+id/manga_info_toggle_less"
-            style="@style/Widget.Tachiyomi.Button.InlineButton"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:paddingStart="8dp"
-            android:paddingEnd="8dp"
-            android:background="@android:color/transparent"
-            android:contentDescription="@string/manga_info_collapse"
-            android:src="@drawable/ic_expand_less_24dp"
-            android:visibility="gone"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/manga_summary_text"
-            app:tint="?android:attr/textColorPrimary"
-            tools:visibility="visible" />
-
-        <HorizontalScrollView
-            android:id="@+id/manga_genres_tags_compact"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:requiresFadingEdge="horizontal"
-            android:scrollbars="none"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_more">
-
-            <com.google.android.material.chip.ChipGroup
-                android:id="@+id/manga_genres_tags_compact_chips"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingStart="16dp"
-                android:paddingTop="8dp"
-                android:paddingEnd="16dp"
-                android:paddingBottom="8dp"
-                app:chipSpacingHorizontal="4dp"
-                app:singleLine="true" />
-
-        </HorizontalScrollView>
-
-        <com.google.android.material.chip.ChipGroup
-            android:id="@+id/manga_genres_tags_full_chips"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginEnd="16dp"
-            android:paddingTop="8dp"
-            android:paddingBottom="8dp"
-            android:visibility="gone"
-            app:chipSpacingHorizontal="4dp"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_less"
-            tools:visibility="gone" />
-
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
+        app:layout_constraintTop_toBottomOf="@id/manga_actions" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/manga_info_header.xml b/app/src/main/res/layout/manga_info_header.xml
index 63f3f62c7..e241c006e 100644
--- a/app/src/main/res/layout/manga_info_header.xml
+++ b/app/src/main/res/layout/manga_info_header.xml
@@ -200,124 +200,12 @@
 
     </LinearLayout>
 
-    <androidx.constraintlayout.widget.ConstraintLayout
+    <eu.kanade.tachiyomi.widget.MangaSummaryView
         android:id="@+id/manga_summary_section"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/manga_actions">
-
-        <TextView
-            android:id="@+id/manga_summary_text"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginEnd="16dp"
-            android:clickable="true"
-            android:ellipsize="end"
-            android:focusable="true"
-            android:maxLines="3"
-            android:textAppearance="?attr/textAppearanceBody2"
-            android:textColor="?android:attr/textColorSecondary"
-            android:textIsSelectable="false"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            tools:text="Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content" />
-
-        <View
-            android:id="@+id/manga_info_scrim"
-            android:layout_width="0dp"
-            android:layout_height="32sp"
-            android:background="@drawable/manga_info_gradient"
-            android:backgroundTint="?android:attr/colorBackground"
-            app:layout_constraintBottom_toBottomOf="@+id/manga_summary_text"
-            app:layout_constraintEnd_toEndOf="@+id/manga_summary_text"
-            app:layout_constraintStart_toStartOf="@+id/manga_summary_text" />
-
-        <View
-            android:id="@+id/manga_info_toggle_more_scrim"
-            android:layout_width="36sp"
-            android:layout_height="18sp"
-            android:background="@drawable/manga_info_more_gradient"
-            android:backgroundTint="?android:attr/colorBackground"
-            app:layout_constraintBottom_toBottomOf="@+id/manga_info_toggle_more"
-            app:layout_constraintEnd_toEndOf="@+id/manga_info_toggle_more"
-            app:layout_constraintStart_toStartOf="@+id/manga_info_toggle_more"
-            app:layout_constraintTop_toTopOf="@+id/manga_info_toggle_more" />
-
-        <ImageButton
-            android:id="@+id/manga_info_toggle_more"
-            style="@style/Widget.Tachiyomi.Button.InlineButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="-4dp"
-            android:paddingStart="0dp"
-            android:paddingEnd="0dp"
-            android:background="@android:color/transparent"
-            android:contentDescription="@string/manga_info_expand"
-            android:src="@drawable/ic_expand_more_24dp"
-            app:layout_constraintBottom_toBottomOf="@id/manga_summary_text"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:tint="?android:attr/textColorPrimary" />
-
-        <ImageButton
-            android:id="@+id/manga_info_toggle_less"
-            style="@style/Widget.Tachiyomi.Button.InlineButton"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:paddingStart="8dp"
-            android:paddingEnd="8dp"
-            android:background="@android:color/transparent"
-            android:contentDescription="@string/manga_info_collapse"
-            android:src="@drawable/ic_expand_less_24dp"
-            android:visibility="gone"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/manga_summary_text"
-            app:tint="?android:attr/textColorPrimary"
-            tools:visibility="visible" />
-
-        <HorizontalScrollView
-            android:id="@+id/manga_genres_tags_compact"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:requiresFadingEdge="horizontal"
-            android:scrollbars="none"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_more">
-
-            <com.google.android.material.chip.ChipGroup
-                android:id="@+id/manga_genres_tags_compact_chips"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingStart="16dp"
-                android:paddingTop="8dp"
-                android:paddingEnd="16dp"
-                android:paddingBottom="8dp"
-                app:chipSpacingHorizontal="4dp"
-                app:singleLine="true" />
-
-        </HorizontalScrollView>
-
-        <com.google.android.material.chip.ChipGroup
-            android:id="@+id/manga_genres_tags_full_chips"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginEnd="16dp"
-            android:paddingTop="8dp"
-            android:paddingBottom="8dp"
-            android:visibility="gone"
-            app:chipSpacingHorizontal="4dp"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_less"
-            tools:visibility="gone" />
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
+        app:layout_constraintTop_toBottomOf="@id/manga_actions" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/manga_summary.xml b/app/src/main/res/layout/manga_summary.xml
new file mode 100644
index 000000000..0559e8a6e
--- /dev/null
+++ b/app/src/main/res/layout/manga_summary.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/description_text"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="16dp"
+        android:ellipsize="end"
+        android:textAppearance="?attr/textAppearanceBody2"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textIsSelectable="false"
+        app:firstBaselineToTopHeight="0dp"
+        app:lastBaselineToBottomHeight="0dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content" />
+
+    <View
+        android:id="@+id/description_scrim"
+        android:layout_width="0dp"
+        android:layout_height="24sp"
+        android:background="@drawable/manga_info_gradient"
+        android:backgroundTint="?android:attr/colorBackground"
+        app:layout_constraintBottom_toBottomOf="@+id/description_text"
+        app:layout_constraintEnd_toEndOf="@+id/description_text"
+        app:layout_constraintStart_toStartOf="@+id/description_text" />
+
+    <View
+        android:id="@+id/toggle_more_scrim"
+        android:layout_width="36sp"
+        android:layout_height="18sp"
+        android:background="@drawable/manga_info_more_gradient"
+        android:backgroundTint="?android:attr/colorBackground"
+        app:layout_constraintBottom_toBottomOf="@+id/toggle_more"
+        app:layout_constraintEnd_toEndOf="@+id/toggle_more"
+        app:layout_constraintStart_toStartOf="@+id/toggle_more"
+        app:layout_constraintTop_toTopOf="@+id/toggle_more" />
+
+    <ImageButton
+        android:id="@+id/toggle_more"
+        style="@style/Widget.Tachiyomi.Button.InlineButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="-6dp"
+        android:background="@android:color/transparent"
+        android:contentDescription="@string/manga_info_expand"
+        android:padding="0dp"
+        android:src="@drawable/anim_caret_down"
+        app:layout_constraintBottom_toBottomOf="@id/description_text"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:tint="?android:attr/textColorPrimary" />
+
+    <HorizontalScrollView
+        android:id="@+id/tag_chips_shrunk_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:requiresFadingEdge="horizontal"
+        android:scrollbars="none"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/toggle_more">
+
+        <com.google.android.material.chip.ChipGroup
+            android:id="@+id/tag_chips_shrunk"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingHorizontal="16dp"
+            android:paddingVertical="8dp"
+            app:chipSpacingHorizontal="4dp"
+            app:singleLine="true" />
+
+    </HorizontalScrollView>
+
+    <com.google.android.material.chip.ChipGroup
+        android:id="@+id/tag_chips_expanded"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="16dp"
+        android:paddingVertical="8dp"
+        android:visibility="gone"
+        app:chipSpacingHorizontal="4dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/toggle_more"
+        tools:visibility="visible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>