diff --git a/Billing.Shared/Billing.Shared.projitems b/Billing.Shared/Billing.Shared.projitems
index a25db1d..7572722 100644
--- a/Billing.Shared/Billing.Shared.projitems
+++ b/Billing.Shared/Billing.Shared.projitems
@@ -38,6 +38,7 @@
     <Compile Include="$(MSBuildThisFileDirectory)UI\Definition.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)UI\GroupStackLayout.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)UI\ItemSelectPage.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)UI\WrapLayout.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Views\AccountPage.xaml.cs">
       <DependentUpon>AccountPage.xaml</DependentUpon>
     </Compile>
diff --git a/Billing.Shared/UI/WrapLayout.cs b/Billing.Shared/UI/WrapLayout.cs
new file mode 100644
index 0000000..20ef995
--- /dev/null
+++ b/Billing.Shared/UI/WrapLayout.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Xamarin.Forms;
+
+namespace Billing.UI
+{
+    public class WrapLayout : Layout<View>
+    {
+        public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(WrapLayout), propertyChanged: OnItemsSourcePropertyChanged);
+        public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create(nameof(ColumnSpacing), typeof(double), typeof(WrapLayout), defaultValue: 5.0, propertyChanged: (obj, _, _) => ((WrapLayout)obj).InvalidateLayout());
+        public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create(nameof(RowSpacing), typeof(double), typeof(WrapLayout), defaultValue: 5.0, propertyChanged: (obj, _, _) => ((WrapLayout)obj).InvalidateLayout());
+
+        private static void OnItemsSourcePropertyChanged(BindableObject obj, object old, object @new)
+        {
+            var itemTemplate = BindableLayout.GetItemTemplate(obj);
+            if (itemTemplate == null)
+            {
+                return;
+            }
+            var layout = (WrapLayout)obj;
+            if (@new == null)
+            {
+                layout.Children.Clear();
+                layout.InvalidateLayout();
+            }
+            else if (@new is IList list)
+            {
+                layout.freezed = true;
+                layout.Children.Clear();
+                foreach (var item in list)
+                {
+                    var child = itemTemplate.CreateContent();
+                    if (child is View view)
+                    {
+                        view.BindingContext = item;
+                        layout.Children.Add(view);
+                    }
+                }
+                layout.freezed = false;
+                layout.InvalidateLayout();
+            }
+        }
+
+        public IList ItemsSource
+        {
+            get => (IList)GetValue(ItemsSourceProperty);
+            set => SetValue(ItemsSourceProperty, value);
+        }
+        public double ColumnSpacing
+        {
+            get => (double)GetValue(ColumnSpacingProperty);
+            set => SetValue(ColumnSpacingProperty, value);
+        }
+        public double RowSpacing
+        {
+            get => (double)GetValue(RowSpacingProperty);
+            set => SetValue(RowSpacingProperty, value);
+        }
+
+        private readonly Dictionary<Size, LayoutData> layoutDataCache = new();
+
+        private bool freezed;
+
+        protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
+        {
+            var data = GetLayoutData(widthConstraint, heightConstraint);
+            if (data.VisibleChildrenCount == 0)
+            {
+                return new SizeRequest();
+            }
+            var totalSize = new Size(
+                data.CellSize.Width * data.Columns + ColumnSpacing * (data.Columns - 1),
+                data.CellSize.Height * data.Rows + RowSpacing * (data.Rows - 1));
+            return new SizeRequest(totalSize);
+        }
+
+        protected override void LayoutChildren(double x, double y, double width, double height)
+        {
+            if (freezed)
+            {
+                return;
+            }
+            var data = GetLayoutData(width, height);
+            if (data.VisibleChildrenCount == 0)
+            {
+                return;
+            }
+
+            double xChild = x;
+            double yChild = y;
+            int row = 0;
+            int column = 0;
+
+            var columnSpacing = ColumnSpacing;
+            var rowSpacing = RowSpacing;
+
+            foreach (View child in Children)
+            {
+                if (!child.IsVisible)
+                {
+                    continue;
+                }
+                LayoutChildIntoBoundingRegion(child, new Rectangle(new Point(xChild, yChild), data.CellSize));
+                if (++column == data.Columns)
+                {
+                    column = 0;
+                    row++;
+                    xChild = x;
+                    yChild += rowSpacing + data.CellSize.Height;
+                }
+                else
+                {
+                    xChild += columnSpacing + data.CellSize.Width;
+                }
+            }
+        }
+
+        protected override void InvalidateLayout()
+        {
+            base.InvalidateLayout();
+            layoutDataCache.Clear();
+        }
+
+        protected override void OnChildMeasureInvalidated()
+        {
+            base.OnChildMeasureInvalidated();
+            layoutDataCache.Clear();
+        }
+
+        private LayoutData GetLayoutData(double width, double height)
+        {
+            Size size = new(width, height);
+            if (layoutDataCache.ContainsKey(size))
+            {
+                return layoutDataCache[size];
+            }
+
+            int visibleChildrenCount = 0;
+            Size maxChildSize = new();
+            LayoutData layoutData;
+
+            foreach (View child in Children)
+            {
+                if (!child.IsVisible)
+                {
+                    continue;
+                }
+                visibleChildrenCount++;
+                SizeRequest childSizeRequest = child.Measure(double.PositiveInfinity, double.PositiveInfinity);
+                maxChildSize.Width = Math.Max(maxChildSize.Width, childSizeRequest.Request.Width);
+                maxChildSize.Height = Math.Max(maxChildSize.Height, childSizeRequest.Request.Height);
+            }
+
+            if (visibleChildrenCount > 0)
+            {
+                var columnSpacing = ColumnSpacing;
+                int rows;
+                int columns;
+                if (double.IsPositiveInfinity(width))
+                {
+                    columns = visibleChildrenCount;
+                    rows = 1;
+                }
+                else
+                {
+                    columns = (int)((width + columnSpacing) / (maxChildSize.Width + columnSpacing));
+                    columns = Math.Max(1, columns);
+                    rows = (visibleChildrenCount + columns - 1) / columns;
+                }
+                Size cellSize = new();
+                if (double.IsPositiveInfinity(width))
+                {
+                    cellSize.Width = maxChildSize.Width;
+                }
+                else
+                {
+                    cellSize.Width = (width - columnSpacing * (columns - 1)) / columns;
+                }
+                if (double.IsPositiveInfinity(height))
+                {
+                    cellSize.Height = maxChildSize.Height;
+                }
+                else
+                {
+                    cellSize.Height = (height - RowSpacing * (rows - 1)) / rows;
+                }
+                layoutData = new LayoutData(visibleChildrenCount, cellSize, rows, columns);
+            }
+            else
+            {
+                layoutData = new LayoutData();
+            }
+
+            layoutDataCache.Add(size, layoutData);
+            return layoutData;
+        }
+    }
+
+    public class LayoutData
+    {
+        public int VisibleChildrenCount { get; set; }
+        public Size CellSize { get; set; }
+        public int Rows { get; set; }
+        public int Columns { get; set; }
+
+        public LayoutData() { }
+        public LayoutData(int count, Size size, int rows, int columns)
+        {
+            VisibleChildrenCount = count;
+            CellSize = size;
+            Rows = rows;
+            Columns = columns;
+        }
+    }
+}