Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

ParallaxCollectionView

A scrollable collection with a hero header that translates at a configurable fraction of the scroll offset, producing the familiar “parallax” effect seen in app store and profile pages. Pure cross-platform implementation — no platform handlers on either host.

On MAUI the control is ParallaxCollectionView, a ContentView that wraps a real CollectionView plus a hero host and drives the translation from CollectionView.Scrolled. On Blazor it is ParallaxList<TItem>, a scrollable container with a JS scroll listener that mutates the hero transform directly (requestAnimationFrame-throttled) so the parallax runs at native scroll framerate without going through component re-renders.

  • NuGet downloads for Shiny.Maui.Controls
  • NuGet downloads for Shiny.Blazor.Controls
Frameworks
.NET MAUI
Blazor

MAUI

ParallaxCollectionView

Blazor

Hero header at rest Header translated on scroll
Hero header at rest on Blazor Header translated on scroll on Blazor
  • Hero header that translates on scroll — Set HeaderTemplate (MAUI) / HeroTemplate (Blazor) to any view; it moves at ParallaxFactor × scroll offset (default 0.5 = half speed).
  • Collapse to sticky — Enable CollapseToSticky with a non-zero MinHeaderHeight and the hero stops translating once it reaches the minimum height, leaving a sticky strip pinned at the top of the list.
  • Fade on scroll — Enable FadeHeaderOnScroll to fade the hero out as it is scrolled past, useful when combining with a separate fixed title.
  • Real CollectionView underneath (MAUI) — Full ItemTemplate / ItemTemplateSelector / EmptyView / SelectionMode / SelectedItem / ScrollTo support; behaves like any MAUI CollectionView.
  • Scrolled event — Both hosts emit ParallaxScrollEventArgs(verticalOffset, headerTranslation, headerVisibleHeight) so you can drive secondary UI (a sticky title that appears once the hero collapses, a fading nav bar, etc.).
  • No platform code — MAUI uses only CollectionView + Grid + transforms. Blazor uses CSS + a tiny JS scroll listener (no Blazor render loop on scroll).
claude plugin marketplace add shinyorg/skills
claude plugin install shiny@shiny

One plugin installs all 35 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.

copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install shiny@shiny

One plugin installs all 35 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.

View Skills Repository
<shiny:ParallaxCollectionView ItemsSource="{Binding Items}"
HeaderHeight="260"
MinHeaderHeight="96"
ParallaxFactor="0.5"
CollapseToSticky="True"
FadeHeaderOnScroll="False"
SelectionMode="Single"
ItemSelectedCommand="{Binding ItemSelectedCommand}">
<shiny:ParallaxCollectionView.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#7C3AED" Offset="0.0" />
<GradientStop Color="#2563EB" Offset="0.5" />
<GradientStop Color="#0EA5E9" Offset="1.0" />
</LinearGradientBrush>
</Grid.Background>
<Label Text="Destinations"
FontSize="28"
FontAttributes="Bold"
TextColor="White"
VerticalOptions="Center"
HorizontalOptions="Center" />
</Grid>
</DataTemplate>
</shiny:ParallaxCollectionView.HeaderTemplate>
<shiny:ParallaxCollectionView.ItemTemplate>
<DataTemplate>
<Border Margin="16,6" Padding="16">
<Label Text="{Binding Title}" FontAttributes="Bold" />
</Border>
</DataTemplate>
</shiny:ParallaxCollectionView.ItemTemplate>
</shiny:ParallaxCollectionView>
<div style="height:600px;">
<ParallaxList TItem="DestinationItem"
Items="@items"
HeaderHeight="260"
MinHeaderHeight="96"
ParallaxFactor="0.5"
CollapseToSticky="true"
FadeHeaderOnScroll="false"
ItemSelected="OnSelected"
Scrolled="OnScrolled">
<HeroTemplate>
<div style="height:100%;background:linear-gradient(135deg,#7C3AED,#2563EB,#0EA5E9);
color:white;display:flex;align-items:center;justify-content:center;
font-size:28px;font-weight:700;">
Destinations
</div>
</HeroTemplate>
<ItemTemplate Context="item">
<div style="margin:6px 16px;padding:16px;background:white;border-radius:14px;">
<strong>@item.Title</strong>
</div>
</ItemTemplate>
</ParallaxList>
</div>
@code {
record DestinationItem(string Title);
List<DestinationItem> items = [];
void OnSelected(DestinationItem item) { }
void OnScrolled(ParallaxScrollEventArgs e) { /* e.HeaderVisibleHeight, etc. */ }
}

Blazor: the <ParallaxList> fills its parent. Place it in a container with a fixed height (px, vh, etc.) so it has something to scroll inside.

Property MAUI Type Blazor Type Default Description
ItemsSource / Items IEnumerable IReadOnlyList<TItem> Collection of items to display
ItemTemplate DataTemplate RenderFragment<TItem> Template for each row
HeaderTemplate / HeroTemplate DataTemplate RenderFragment Template for the parallax hero header
EmptyView / EmptyTemplate object / DataTemplate RenderFragment Shown when the source is null or empty
HeaderHeight double double 240 Height of the hero header in px
MinHeaderHeight double double 0 Minimum visible height when CollapseToSticky is true
ParallaxFactor double double 0.5 Fraction of the scroll offset applied to the hero translation (0 = pinned, 1 = scrolls with content)
CollapseToSticky bool bool false When true, the hero stops translating at MinHeaderHeight and stays pinned
FadeHeaderOnScroll bool bool false Fade the hero from 100% → 0% opacity as it scrolls past
SelectionMode SelectionMode None MAUI only — passthrough to inner CollectionView
SelectedItem object MAUI only — TwoWay selected item
ItemSelectedCommand ICommand MAUI only — fired on selection change
ItemSelected EventCallback<TItem> Blazor only — fired on row click
Height string Blazor only — CSS height for the scroll container; omit to fill parent

Both hosts fire a Scrolled event with ParallaxScrollEventArgs:

  • VerticalOffset — current CollectionView/scroll-container scrollTop in px
  • HeaderTranslation — negative px translation currently applied to the hero (clamped if CollapseToSticky)
  • HeaderVisibleHeight — how many px of the hero are still visible (HeaderHeight + HeaderTranslation, floored at MinHeaderHeight)

Use this to drive a sticky title that appears once the hero is mostly hidden, fade a navigation chrome, or update progress indicators.

  • The hero is laid out at row 0 of the host grid (MAUI) / absolutely positioned at the top of the scroll container (Blazor). The list content sits below it via a transparent CollectionView header (MAUI) or a margin-top matching HeaderHeight (Blazor), so items scroll over the hero when it has been pushed out of view.
  • ParallaxFactor = 0 pins the hero in place (no parallax).
  • ParallaxFactor = 1 makes the hero scroll with the content (no parallax). Use this if you want a normal scrolling header.
  • ParallaxFactor = 0.5 (default) gives a smooth half-speed effect that reads as parallax on most screens.
  • CollapseToSticky requires MinHeaderHeight > 0 to be meaningful — set both together.
  • FadeHeaderOnScroll and CollapseToSticky are independent and can be combined: the hero collapses to its minimum height and then fades out.
  • MAUI: setting ItemsLayout (e.g. <GridItemsLayout Span="2" />) on the control passes through to the inner CollectionView, so the list portion can be a multi-column grid while the hero stays full-width.
  • Blazor: the scroll listener runs in JS and updates transform/opacity on the hero element directly using requestAnimationFrame, so 60 fps scroll behavior is preserved without re-rendering Razor components.
  • Wrap the hero in a Grid (MAUI) or <div> (Blazor) and use a gradient Background or CSS background instead of a static image — gradients render cheaply during the translation.
  • For the “sticky title that appears once the hero collapses” pattern, listen to Scrolled and toggle the visibility of a separate header element when HeaderVisibleHeight drops below a threshold (e.g. MinHeaderHeight + 8).
  • If you need a multi-column grid of items under the hero, set ItemsLayout (MAUI) to a GridItemsLayout. The hero remains a full-width header.