diff --git a/django-stubs/contrib/admin/options.pyi b/django-stubs/contrib/admin/options.pyi
index 9d306e8d83..d5cdb602a3 100644
--- a/django-stubs/contrib/admin/options.pyi
+++ b/django-stubs/contrib/admin/options.pyi
@@ -56,7 +56,7 @@ class IncorrectLookupParameters(Exception): ...
 FORMFIELD_FOR_DBFIELD_DEFAULTS: Any
 csrf_protect_m: Any
 
-_FieldGroups: TypeAlias = Sequence[str | Sequence[str]]
+_FieldGroups: TypeAlias = _ListOrTuple[str | _ListOrTuple[str]]
 
 @type_check_only
 class _OptionalFieldOpts(TypedDict, total=False):
@@ -67,9 +67,6 @@ class _OptionalFieldOpts(TypedDict, total=False):
 class _FieldOpts(_OptionalFieldOpts, total=True):
     fields: _FieldGroups
 
-# Workaround for mypy issue, a Sequence type should be preferred here.
-# https://github.com/python/mypy/issues/8921
-# _FieldsetSpec = Sequence[Tuple[Optional[str], _FieldOpts]]
 _FieldsetSpec: TypeAlias = _ListOrTuple[tuple[_StrOrPromise | None, _FieldOpts]]
 _ListFilterT: TypeAlias = (
     type[ListFilter]
@@ -82,7 +79,8 @@ _ListFilterT: TypeAlias = (
 # Generic type specifically for models, for use in BaseModelAdmin and subclasses
 # https://github.com/typeddjango/django-stubs/issues/482
 _ModelT = TypeVar("_ModelT", bound=Model)
-_DisplayT: TypeAlias = _ListOrTuple[str | Callable[[_ModelT], str | bool]]
+_DisplayT: TypeAlias = str | Callable[[_ModelT], str | bool]
+_ListDisplayT: TypeAlias = _ListOrTuple[_DisplayT[_ModelT]]
 
 # Options `form`, `list_display`, `list_display_links` and `actions` are not marked as `ClassVar` due to the
 # limitations of the current type system: `ClassVar` cannot contain type variables.
@@ -130,7 +128,7 @@ class BaseModelAdmin(Generic[_ModelT]):
     def get_readonly_fields(self, request: HttpRequest, obj: _ModelT | None = ...) -> _ListOrTuple[str]: ...
     def get_prepopulated_fields(self, request: HttpRequest, obj: _ModelT | None = ...) -> dict[str, Sequence[str]]: ...
     def get_queryset(self, request: HttpRequest) -> QuerySet[_ModelT]: ...
-    def get_sortable_by(self, request: HttpRequest) -> _DisplayT[_ModelT]: ...
+    def get_sortable_by(self, request: HttpRequest) -> _ListDisplayT[_ModelT]: ...
     @overload
     @deprecated("None value for the request parameter will be removed in Django 6.0.")
     def lookup_allowed(self, lookup: str, value: str, request: None = None) -> bool: ...
@@ -150,8 +148,8 @@ _ModelAdmin = TypeVar("_ModelAdmin", bound=ModelAdmin[Any])
 _ActionCallable: TypeAlias = Callable[[_ModelAdmin, HttpRequest, QuerySet[_ModelT]], HttpResponseBase | None]
 
 class ModelAdmin(BaseModelAdmin[_ModelT]):
-    list_display: _DisplayT[_ModelT]
-    list_display_links: _DisplayT[_ModelT] | None
+    list_display: _ListDisplayT[_ModelT]
+    list_display_links: _ListDisplayT[_ModelT] | None
     list_filter: ClassVar[_ListOrTuple[_ListFilterT]]
     list_select_related: ClassVar[bool | _ListOrTuple[str]]
     list_per_page: ClassVar[int]
@@ -220,8 +218,10 @@ class ModelAdmin(BaseModelAdmin[_ModelT]):
         self, request: HttpRequest, default_choices: list[tuple[str, str]] = ...
     ) -> list[tuple[str, str]]: ...
     def get_action(self, action: Callable | str) -> tuple[Callable[..., str], str, str] | None: ...
-    def get_list_display(self, request: HttpRequest) -> _DisplayT[_ModelT]: ...
-    def get_list_display_links(self, request: HttpRequest, list_display: _DisplayT[_ModelT]) -> _DisplayT[_ModelT]: ...
+    def get_list_display(self, request: HttpRequest) -> _ListDisplayT[_ModelT]: ...
+    def get_list_display_links(
+        self, request: HttpRequest, list_display: _ListDisplayT[_ModelT]
+    ) -> _ListDisplayT[_ModelT]: ...
     def get_list_filter(self, request: HttpRequest) -> _ListOrTuple[_ListFilterT]: ...
     def get_list_select_related(self, request: HttpRequest) -> bool | _ListOrTuple[str]: ...
     def get_search_fields(self, request: HttpRequest) -> _ListOrTuple[str]: ...
diff --git a/django-stubs/contrib/admin/utils.pyi b/django-stubs/contrib/admin/utils.pyi
index 34acd72fd8..98f250fd44 100644
--- a/django-stubs/contrib/admin/utils.pyi
+++ b/django-stubs/contrib/admin/utils.pyi
@@ -5,7 +5,7 @@ from typing import Any, Literal, TypeVar, overload, type_check_only
 from uuid import UUID
 
 from _typeshed import Unused
-from django.contrib.admin.options import BaseModelAdmin
+from django.contrib.admin.options import BaseModelAdmin, _DisplayT, _FieldGroups, _FieldsetSpec, _ListDisplayT, _ModelT
 from django.contrib.admin.sites import AdminSite
 from django.db.models.base import Model
 from django.db.models.deletion import Collector
@@ -31,8 +31,11 @@ def prepare_lookup_value(
 def build_q_object_from_lookup_parameters(parameters: dict[str, list[str]]) -> Q: ...
 def quote(s: int | str | UUID) -> str: ...
 def unquote(s: str) -> str: ...
-def flatten(fields: Any) -> list[Callable | str]: ...
-def flatten_fieldsets(fieldsets: Any) -> list[Callable | str]: ...
+@overload
+def flatten(fields: _FieldGroups) -> list[str]: ...
+@overload
+def flatten(fields: _ListDisplayT[_ModelT]) -> list[_DisplayT[_ModelT]]: ...
+def flatten_fieldsets(fieldsets: _FieldsetSpec) -> list[str]: ...
 def get_deleted_objects(
     objs: Sequence[Model | None] | QuerySet[Model], request: HttpRequest, admin_site: AdminSite
 ) -> tuple[list[str], dict[str, int], set[str], list[str]]: ...
diff --git a/django-stubs/contrib/admin/views/main.pyi b/django-stubs/contrib/admin/views/main.pyi
index 881b3dbf4c..fb6ad1b31f 100644
--- a/django-stubs/contrib/admin/views/main.pyi
+++ b/django-stubs/contrib/admin/views/main.pyi
@@ -3,7 +3,7 @@ from typing import Any, Literal
 
 from django import forms
 from django.contrib.admin.filters import ListFilter
-from django.contrib.admin.options import ModelAdmin, _DisplayT, _ListFilterT
+from django.contrib.admin.options import ModelAdmin, _ListDisplayT, _ListFilterT
 from django.db.models.base import Model
 from django.db.models.expressions import Expression
 from django.db.models.options import Options
@@ -26,8 +26,8 @@ class ChangeList:
     opts: Options
     lookup_opts: Options
     root_queryset: QuerySet
-    list_display: _DisplayT
-    list_display_links: _DisplayT
+    list_display: _ListDisplayT
+    list_display_links: _ListDisplayT
     list_filter: Sequence[_ListFilterT]
     date_hierarchy: Any
     search_fields: Sequence[str]
@@ -58,8 +58,8 @@ class ChangeList:
         self,
         request: HttpRequest,
         model: type[Model],
-        list_display: _DisplayT,
-        list_display_links: _DisplayT,
+        list_display: _ListDisplayT,
+        list_display_links: _ListDisplayT,
         list_filter: Sequence[_ListFilterT],
         date_hierarchy: str | None,
         search_fields: Sequence[str],
diff --git a/tests/assert_type/contrib/admin/test_utils.py b/tests/assert_type/contrib/admin/test_utils.py
new file mode 100644
index 0000000000..bc57e15d58
--- /dev/null
+++ b/tests/assert_type/contrib/admin/test_utils.py
@@ -0,0 +1,82 @@
+from __future__ import annotations
+
+from django import http
+from django.contrib import admin
+from django.contrib.admin.options import _DisplayT
+from django.contrib.admin.utils import flatten, flatten_fieldsets
+from django.db import models
+from typing_extensions import assert_type
+
+
+@admin.display(description="Name")
+def upper_case_name(obj: Person) -> str:
+    return f"{obj.first_name} {obj.last_name}".upper()  # pyright: ignore[reportUnknownMemberType]
+
+
+class Person(models.Model):
+    first_name = models.CharField(max_length=None)  # pyright: ignore[reportUnknownVariableType]
+    last_name = models.CharField(max_length=None)  # pyright: ignore[reportUnknownVariableType]
+    birthday = models.DateField()  # pyright: ignore[reportUnknownVariableType]
+
+
+class PersonListAdmin(admin.ModelAdmin[Person]):
+    fields = [["first_name", "last_name"], "birthday"]
+    list_display = [upper_case_name, "birthday"]
+
+
+class PersonTupleAdmin(admin.ModelAdmin[Person]):
+    fields = (("first_name", "last_name"), "birthday")
+    list_display = (upper_case_name, "birthday")
+
+
+class PersonFieldsetListAdmin(admin.ModelAdmin[Person]):
+    fieldsets = [
+        (
+            "Personal Details",
+            {
+                "description": "Personal details of a person.",
+                "fields": [["first_name", "last_name"], "birthday"],
+            },
+        )
+    ]
+
+
+class PersonFieldsetTupleAdmin(admin.ModelAdmin[Person]):
+    fieldsets = (
+        (
+            "Personal Details",
+            {
+                "description": "Personal details of a person.",
+                "fields": (("first_name", "last_name"), "birthday"),
+            },
+        ),
+    )
+
+
+request = http.HttpRequest()
+admin_site = admin.AdminSite()
+person_list_admin = PersonListAdmin(Person, admin_site)
+person_tuple_admin = PersonTupleAdmin(Person, admin_site)
+person_fieldset_list_admin = PersonFieldsetListAdmin(Person, admin_site)
+person_fieldset_tuple_admin = PersonFieldsetTupleAdmin(Person, admin_site)
+
+# For some reason, pyright cannot see that these are not `None`.
+assert person_list_admin.fields is not None
+assert person_tuple_admin.fields is not None
+assert person_fieldset_list_admin.fieldsets is not None
+assert person_fieldset_tuple_admin.fieldsets is not None
+
+assert_type(flatten(person_list_admin.fields), list[str])
+assert_type(flatten(person_list_admin.get_fields(request)), list[str])
+assert_type(flatten(person_tuple_admin.fields), list[str])
+assert_type(flatten(person_tuple_admin.get_fields(request)), list[str])
+
+assert_type(flatten(person_list_admin.list_display), list[_DisplayT[Person]])
+assert_type(flatten(person_list_admin.get_list_display(request)), list[_DisplayT[Person]])
+assert_type(flatten(person_tuple_admin.list_display), list[_DisplayT[Person]])
+assert_type(flatten(person_tuple_admin.get_list_display(request)), list[_DisplayT[Person]])
+
+assert_type(flatten_fieldsets(person_fieldset_list_admin.fieldsets), list[str])
+assert_type(flatten_fieldsets(person_fieldset_list_admin.get_fieldsets(request)), list[str])
+assert_type(flatten_fieldsets(person_fieldset_tuple_admin.fieldsets), list[str])
+assert_type(flatten_fieldsets(person_fieldset_tuple_admin.get_fieldsets(request)), list[str])