Your focusable element is attached correctly. The problem is that there is only one of it.
On tvOS a ScrollView scrolls as a side effect of focus moving: the focus engine picks a new target, and if that target is off screen the container scrolls to bring it on. With a single focusable view there is no second target to move to, so nothing is ever revealed and the view never budges. One focusable view behaves exactly like none. That is also why your UIFocusSystem.movementDidFailNotification is firing and why -UIFocusLoggingEnabled YES is complaining — the movement really is failing, because there is nowhere to go.
Apple documents the mechanism, but in UIKit rather than SwiftUI, on UIFocusItemScrollableContainer — "A type of focus item container that supports automatic scrolling of focusable content", and "The focus engine scrolls the container to keep items onscreen as they become focused." You can write SwiftUI for a long time without meeting that protocol. https://developer.apple.com/documentation/uikit/uifocusitemscrollablecontainer
I don't know why the Xcode preview scrolls when the device won't — my assumption is that the preview isn't driving the focus engine, but I haven't confirmed that, so I wouldn't read much into the preview either way.
I should say plainly what this does not fix: it will not make a single long Text scroll. If you need one unbroken text view, the UITextView approach further down this thread is the way to do it. What works in SwiftUI is giving the focus engine more than one place to land, which means splitting the content:
ScrollView {
VStack(alignment: .leading, spacing: 16) {
ForEach(paragraphs, id: \.self) { para in
Text(para).focusable(true)
}
}
}
Two traps I hit doing this in a shipping tvOS app, both of which cost me a round on the device.
Putting .focusable(true) on the enclosing VStack instead of the rows looks like the fix and causes the bug — it collapses the whole block to one target. I had written that version myself, having correctly worked out that a scroll view with nothing focusable cannot scroll, and then not noticed that one is the same as none.
And the scroll only ever travels to a view that takes focus, so anything above your topmost focusable view is unreachable once it scrolls off the top. If there is a title above the content, make it focusable too or you can go down and never come back up.
The cost is a focus ring on prose when the d-pad lands on it. On balance I kept it, since that ring is the only feedback that the remote did anything.
Observed on an Apple TV 4K (3rd generation) running tvOS 27, on an app that builds back to tvOS 17.
Longer write-up with the two traps and a pane whose scrollability turned out to depend on StoreKit returning products: https://mcmizzle.com/blog/tvos-scrollview-wont-scroll/