diff --git a/samples/WinUI.TableView.SampleApp/MainPage.xaml b/samples/WinUI.TableView.SampleApp/MainPage.xaml
index 701278a9..2f0fe4a3 100644
--- a/samples/WinUI.TableView.SampleApp/MainPage.xaml
+++ b/samples/WinUI.TableView.SampleApp/MainPage.xaml
@@ -139,6 +139,11 @@
+
+
+
+
+
diff --git a/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs b/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs
index ef811570..36b91ac1 100644
--- a/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs
+++ b/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs
@@ -112,6 +112,7 @@ private void OnNavigationSelectionChanged(NavigationView sender, NavigationViewS
"Editing" => typeof(EditingPage),
"Sorting" => typeof(SortingPage),
"Custom Sorting" => typeof(CustomizeSortingPage),
+ "Grouping" => typeof(GroupingPage),
"Data Export" => typeof(ExportPage),
"Large Dataset" => typeof(LargeDataPage),
"Conditional Cell Styling" => typeof(ConditionalStylingPage),
diff --git a/samples/WinUI.TableView.SampleApp/Pages/GroupingPage.xaml b/samples/WinUI.TableView.SampleApp/Pages/GroupingPage.xaml
new file mode 100644
index 00000000..38355cb0
--- /dev/null
+++ b/samples/WinUI.TableView.SampleApp/Pages/GroupingPage.xaml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<tv:TableView ItemsSource="{Binding Items}"
+ GroupByPath="$(GroupByPath)"
+ ShowGroupHeaders="$(ShowGroupHeaders)"
+ ShowGroupItemCount="$(ShowGroupItemCount)"
+ GroupSortDirection="$(GroupSortDirection)">
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/WinUI.TableView.SampleApp/Pages/GroupingPage.xaml.cs b/samples/WinUI.TableView.SampleApp/Pages/GroupingPage.xaml.cs
new file mode 100644
index 00000000..bbd87786
--- /dev/null
+++ b/samples/WinUI.TableView.SampleApp/Pages/GroupingPage.xaml.cs
@@ -0,0 +1,54 @@
+using Microsoft.UI.Xaml.Controls;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace WinUI.TableView.SampleApp.Pages;
+
+public sealed partial class GroupingPage : Page, INotifyPropertyChanged
+{
+ private string? _groupByPath = "Department";
+ private SortDirection _groupSortDirection = SortDirection.Ascending;
+
+ public GroupingPage()
+ {
+ InitializeComponent();
+ }
+
+ public string? GroupByPath
+ {
+ get => _groupByPath;
+ set { _groupByPath = value; OnPropertyChanged(); }
+ }
+
+ public SortDirection GroupSortDirection
+ {
+ get => _groupSortDirection;
+ set { _groupSortDirection = value; OnPropertyChanged(); }
+ }
+
+ private void OnGroupBySelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (sender is ComboBox comboBox && comboBox.SelectedItem is ComboBoxItem item)
+ {
+ var path = item.Tag?.ToString();
+ GroupByPath = string.IsNullOrEmpty(path) ? null : path;
+ }
+ }
+
+ private void OnSortDirectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (sender is ComboBox comboBox)
+ {
+ GroupSortDirection = comboBox.SelectedIndex == 0
+ ? SortDirection.Ascending
+ : SortDirection.Descending;
+ }
+ }
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
diff --git a/src/TableView.Grouping.cs b/src/TableView.Grouping.cs
new file mode 100644
index 00000000..61db1a35
--- /dev/null
+++ b/src/TableView.Grouping.cs
@@ -0,0 +1,525 @@
+using Microsoft.UI.Xaml;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using WinUI.TableView.Extensions;
+
+namespace WinUI.TableView;
+
+///
+/// Partial class for TableView that contains row grouping logic.
+///
+public partial class TableView
+{
+ #region Grouping Dependency Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty GroupByPathProperty = DependencyProperty.Register(nameof(GroupByPath), typeof(string), typeof(TableView), new PropertyMetadata(null, OnGroupByPathChanged));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty ShowGroupHeadersProperty = DependencyProperty.Register(nameof(ShowGroupHeaders), typeof(bool), typeof(TableView), new PropertyMetadata(true, OnShowGroupHeadersChanged));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty GroupSortDirectionProperty = DependencyProperty.Register(nameof(GroupSortDirection), typeof(SortDirection), typeof(TableView), new PropertyMetadata(SortDirection.Ascending, OnGroupSortDirectionChanged));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty ShowGroupItemCountProperty = DependencyProperty.Register(nameof(ShowGroupItemCount), typeof(bool), typeof(TableView), new PropertyMetadata(true, OnShowGroupItemCountChanged));
+
+ ///
+ /// Gets or sets the property path used to group items in the TableView.
+ ///
+ public string? GroupByPath
+ {
+ get => (string?)GetValue(GroupByPathProperty);
+ set => SetValue(GroupByPathProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether group headers are shown.
+ ///
+ public bool ShowGroupHeaders
+ {
+ get => (bool)GetValue(ShowGroupHeadersProperty);
+ set => SetValue(ShowGroupHeadersProperty, value);
+ }
+
+ ///
+ /// Gets or sets the sorting direction used for grouping.
+ ///
+ public SortDirection GroupSortDirection
+ {
+ get => (SortDirection)GetValue(GroupSortDirectionProperty);
+ set => SetValue(GroupSortDirectionProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether group headers show item counts.
+ ///
+ public bool ShowGroupItemCount
+ {
+ get => (bool)GetValue(ShowGroupItemCountProperty);
+ set => SetValue(ShowGroupItemCountProperty, value);
+ }
+
+ ///
+ /// Handles changes to the GroupByPath property.
+ ///
+ private static void OnGroupByPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is TableView tableView)
+ {
+ tableView.EnsureGroupingSortDescription();
+ tableView.RebuildDisplayedItems();
+ }
+ }
+
+ ///
+ /// Handles changes to the ShowGroupHeaders property.
+ ///
+ private static void OnShowGroupHeadersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is TableView tableView)
+ {
+ tableView.RebuildDisplayedItems();
+ }
+ }
+
+ ///
+ /// Handles changes to the GroupSortDirection property.
+ ///
+ private static void OnGroupSortDirectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is TableView tableView)
+ {
+ tableView.EnsureGroupingSortDescription();
+ tableView.RebuildDisplayedItems();
+ }
+ }
+
+ ///
+ /// Handles changes to the ShowGroupItemCount property.
+ ///
+ private static void OnShowGroupItemCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is TableView tableView)
+ {
+ tableView.RebuildDisplayedItems();
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Represents a sentinel item inserted into the display list to render a group header row.
+ ///
+ private sealed class GroupHeaderRowItem
+ {
+ public required object GroupKey { get; init; }
+
+ public required string Header { get; init; }
+ }
+
+ ///
+ /// Sentinel object used as a dictionary key when the group property value is null.
+ ///
+ private static readonly object NullGroupKey = new();
+
+ private readonly ObservableCollection