Pattern · Navigation Bars
NewScope Header SwiftUI Block
A feed header: the title, scopes as text tabs with a sliding underline, filter chips that scroll, and a Filters button with its count.
The header of a feed. Scopes such as For you, Following and Saved sit as text tabs with an underline that slides to the one you pick, and filter chips scroll sideways beside a Filters button that counts what is on. It pins over the feed as a section header and shows a hairline once it is stuck.
- SwiftUI
- iOS 17.0+
- scope-header
$199 once, every piece included. See how installing works · Sign in
01
What's included
SwiftUI source you own, built on Apple frameworks only.
- Scopes as text tabs with an underline that slides between them
- Filter chips that scroll sideways and toggle with a check
- A Filters button whose count rolls as filters change
- Pins as a section header and shows a hairline when stuck
- Bindings for the scope and the chips, with loading chips
02
Implementation notes
How it behaves, what it handles, and the decisions behind it.
The title sits in the brand's display face over the feed's scopes ("For you", "Following", "Saved"), text tabs on a hairline with an underline that slides to the one you pick; a scope can show how many items are new. Under them a row of filter chips scrolls sideways: a chip on fills in textPrimary with a check, and the Filters button at the end of the row shows how many filters are on, rolling to each new count. Your app owns the scope and the chips through two bindings and reloads the feed when they change. The header brings its own ground so it can pin as a section header, and a hairline appears under it once it is stuck over the feed. Pass nil chips while they load and the row shows grey chips in their place.
Notes
ScopeHeader(title:scopes:scope:chips:selectedChips:filterCount:isStuck:onFilters:)takes the title, the scopes, a binding to the selected scope's id, the chips and a binding to the set of chips that are on.ScopeHeader.Scopehas anid, atitleand an optionalcountof new items, shown after the title intextTertiary.ScopeHeader.Chiphas anid, atitleand an optional SF Symbol.sampleScopesandsampleChipsare the Reading tab's.chips: []hides the chip row;nilshows it loading.filterCountis the figure on the Filters button. It defaults to the chips that are on; pass your own when the filter sheet holds more than the chips.onFiltersopens your full filter sheet; nil hides the button.- Pinning: place the header in
Section(header:)insideLazyVStack(pinnedViews: .sectionHeaders)and give the stack.coordinateSpace(ScopeHeader.feedSpace). The header then notices when it is stuck and shows its hairline. Or passisStuckfrom your own scroll tracking. - Scopes that don't fit the width (large text sizes) scroll sideways with the chips' behaviour.
- Picking a scope and toggling a chip each tick with a selection haptic.
- VoiceOver reads the title as a header, each scope as a button with Selected on the current one and "4 new" as its value, each chip with Selected when on and a hint, and the Filters button as "Filters, 2 on". Every control has a 44 point target.
- Reduce Motion turns the underline slide and chip changes into short fades.
ScopeHeaderExampleat the end ofScopeHeader.swiftis the Reading tab in the preview: the header pinned over a feed of articles that follows the scope and the chips. PasswrapsInNavigationStack: falseto push it in your own stack.
Theming
- Reads
SPThemefrom@Environment(\.spTheme)and requires thedesign-systemitem. - The title is the one display moment: the brand's display face (
type.face) at 28 points,type.displayWeightcapped at semibold, intextPrimary. When the face isn't bundled it falls back to SF intype.figureDesign, or intype.textDesignwhen figures are monospaced. - Scopes are
type.body: semiboldtextPrimarywhen selected, regulartextSecondaryotherwise, with a 2 pointtextPrimaryunderline that moves withmatchedGeometryEffectandmotion.hero. AnSPHairlineruns under them. - Chips are
type.subheadmedium onfillwith a 1 pointseparatorstroke; a chip that is on istextPrimarywith its label inbg. - The Filters button is
fillwith aseparatorring; its count is theaccentwithaccentOnfigures intype.caption, ringed inbg: the block's only accent. - The header's ground is
bg, so the feed can run under it. Chip changes usemotion.snappy, adapted for Reduce Motion.
03
Keep building
Pieces that work well next to this one.