diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,30 @@
 Brick changelog
 ---------------
 
+0.58
+----
+
+Package changes:
+ * Updated dependency constraints to build on GHC 9.0.1 (thanks Ondřej
+   Súkup)
+
+API changes:
+ * The FileBrowser module now exports individual functions for
+   each of the events that it handles. This allows end users to
+   trigger the behaviors directly rather than relying on the built-in
+   `handleFileBrowserEvent` function. The documentation has been updated
+   to indicate which functions are triggered by each key event. (Thanks
+   David B. Lamkins)
+
+Other changes:
+ * The `List` module's `listFindBy` function now attempts to find a
+   match anywhere in the list rather than just somewhere between the
+   cursor and the end of the list.
+ * The `FileBrowser` now positions a cursor at the beginning of the
+   selected entry when the file browser is focused. (thanks Mario Lang)
+ * The user guide's viewport visibility example got an important
+   syntactic fix. (thanks Mario Lang)
+
 0.57.1
 ------
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -67,6 +67,7 @@
 | [`wrapping-editor`](https://github.com/ta0kira/wrapping-editor) | An embeddable editor with support for Brick |
 | [`git-brunch`](https://github.com/andys8/git-brunch) | A git branch checkout utility |
 | [`hascard`](https://github.com/Yvee1/hascard) | A program for reviewing "flash card" notes |
+| [`ttyme`](https://github.com/evuez/ttyme) | A TUI for [Harvest](https://www.getharvest.com/) |
 
 These third-party packages also extend `brick`:
 
diff --git a/brick.cabal b/brick.cabal
--- a/brick.cabal
+++ b/brick.cabal
@@ -1,5 +1,5 @@
 name:                brick
-version:             0.57.1
+version:             0.58
 synopsis:            A declarative terminal user interface library
 description:
   Write terminal user interfaces (TUIs) painlessly with 'brick'! You
@@ -113,7 +113,7 @@
     Brick.Types.Internal
     Brick.Widgets.Internal
 
-  build-depends:       base <= 4.14.1.0,
+  build-depends:       base <= 4.15,
                        vty >= 5.31,
                        transformers,
                        data-clist >= 0.1,
diff --git a/docs/guide.rst b/docs/guide.rst
--- a/docs/guide.rst
+++ b/docs/guide.rst
@@ -1205,7 +1205,7 @@
    -- Assuming that App uses 'Name' for its resource names:
    data Name = Viewport1
    let w = viewport Viewport1 Horizontal $
-           (visible $ str "Hello," <+> (str " world!")
+           (visible $ str "Hello,") <+> (str " world!")
 
 This example requests that the ``Viewport1`` viewport be scrolled so
 that "Hello," is visible. We could extend this example with a value
diff --git a/src/Brick/Widgets/Core.hs b/src/Brick/Widgets/Core.hs
--- a/src/Brick/Widgets/Core.hs
+++ b/src/Brick/Widgets/Core.hs
@@ -862,7 +862,12 @@
 
 -- | When rendering the specified widget, force all attribute lookups
 -- in the attribute map to use the value currently assigned to the
--- specified attribute name.
+-- specified attribute name. This means that the attribute lookups will
+-- behave as if they all used the name specified here. That further
+-- means that the resolved attribute will still inherit from its parent
+-- entry in the attribute map as would normally be the case. If you
+-- want to have more control over the resulting attribute, consider
+-- 'modifyDefAttr'.
 forceAttr :: AttrName -> Widget n -> Widget n
 forceAttr an p =
     Widget (hSize p) (vSize p) $ do
diff --git a/src/Brick/Widgets/FileBrowser.hs b/src/Brick/Widgets/FileBrowser.hs
--- a/src/Brick/Widgets/FileBrowser.hs
+++ b/src/Brick/Widgets/FileBrowser.hs
@@ -65,6 +65,19 @@
   , updateFileBrowserSearch
   , setFileBrowserEntryFilter
 
+  -- * Actions
+  , actionFileBrowserBeginSearch
+  , actionFileBrowserSelectEnter
+  , actionFileBrowserSelectCurrent
+  , actionFileBrowserListPageUp
+  , actionFileBrowserListPageDown
+  , actionFileBrowserListHalfPageUp
+  , actionFileBrowserListHalfPageDown
+  , actionFileBrowserListTop
+  , actionFileBrowserListBottom
+  , actionFileBrowserListNext
+  , actionFileBrowserListPrev
+
   -- * Handling events
   , handleFileBrowserEvent
   , maybeSelectCurrentEntry
@@ -540,21 +553,81 @@
 --
 -- Events handled regardless of mode:
 --
--- * @Enter@, @Space@: set the file browser's selected entry
---   ('fileBrowserSelection') for use by the calling application,
---   subject to 'fileBrowserSelectable'.
--- * @Ctrl-n@: select the next entry
--- * @Ctrl-p@: select the previous entry
--- * 'List' navigation keys
+-- * @Ctrl-b@: 'actionFileBrowserListPageUp'
+-- * @Ctrl-f@: 'actionFileBrowserListPageDown'
+-- * @Ctrl-d@: 'actionFileBrowserListHalfPageDown'
+-- * @Ctrl-u@: 'actionFileBrowserListHalfPageUp'
+-- * @g@: 'actionFileBrowserListTop'
+-- * @G@: 'actionFileBrowserListBottom'
+-- * @j@: 'actionFileBrowserListNext'
+-- * @k@: 'actionFileBrowserListPrev'
+-- * @Ctrl-n@: 'actionFileBrowserListNext'
+-- * @Ctrl-p@: 'actionFileBrowserListPrev'
 --
 -- Events handled only in normal mode:
 --
--- * @/@: enter search mode
+-- * @/@: 'actionFileBrowserBeginSearch'
+-- * @Enter@: 'actionFileBrowserSelectEnter'
+-- * @Space@: 'actionFileBrowserSelectCurrent'
 --
 -- Events handled only in search mode:
 --
 -- * @Esc@, @Ctrl-C@: cancel search mode
 -- * Text input: update search string
+
+actionFileBrowserBeginSearch :: FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserBeginSearch b =
+    return $ updateFileBrowserSearch (const $ Just "") b
+
+actionFileBrowserSelectEnter :: FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserSelectEnter b =
+    maybeSelectCurrentEntry b
+
+actionFileBrowserSelectCurrent :: FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserSelectCurrent b =
+    selectCurrentEntry b
+
+actionFileBrowserListPageUp :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserListPageUp b = do
+    let old = b ^. fileBrowserEntriesL
+    new <- listMovePageUp old
+    return $ b & fileBrowserEntriesL .~ new
+
+actionFileBrowserListPageDown :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserListPageDown b = do
+    let old = b ^. fileBrowserEntriesL
+    new <- listMovePageDown old
+    return $ b & fileBrowserEntriesL .~ new
+
+actionFileBrowserListHalfPageUp :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserListHalfPageUp b = do
+    let old = b ^. fileBrowserEntriesL
+    new <- listMoveByPages (-0.5::Double) old
+    return $ b & fileBrowserEntriesL .~ new
+
+actionFileBrowserListHalfPageDown :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserListHalfPageDown b = do
+    let old = b ^. fileBrowserEntriesL
+    new <- listMoveByPages (0.5::Double) old
+    return $ b & fileBrowserEntriesL .~ new
+
+actionFileBrowserListTop :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserListTop b =
+    return $ b & fileBrowserEntriesL %~ listMoveTo 0
+
+actionFileBrowserListBottom :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserListBottom b = do
+    let sz = length (listElements $ b^.fileBrowserEntriesL)
+    return $ b & fileBrowserEntriesL %~ listMoveTo (sz - 1)
+
+actionFileBrowserListNext :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserListNext b =
+    return $ b & fileBrowserEntriesL %~ listMoveBy 1
+
+actionFileBrowserListPrev :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+actionFileBrowserListPrev b =
+    return $ b & fileBrowserEntriesL %~ listMoveBy (-1)
+
 handleFileBrowserEvent :: (Ord n) => Vty.Event -> FileBrowser n -> EventM n (FileBrowser n)
 handleFileBrowserEvent e b =
     if fileBrowserIsSearching b
@@ -587,48 +660,39 @@
     case e of
         Vty.EvKey (Vty.KChar '/') [] ->
             -- Begin file search
-            return $ updateFileBrowserSearch (const $ Just "") b
+            actionFileBrowserBeginSearch b
         Vty.EvKey Vty.KEnter [] ->
             -- Select file or enter directory
-            maybeSelectCurrentEntry b
+            actionFileBrowserSelectEnter b
         Vty.EvKey (Vty.KChar ' ') [] ->
             -- Select entry
-            selectCurrentEntry b
+            actionFileBrowserSelectCurrent b
         _ ->
             handleFileBrowserEventCommon e b
 
 handleFileBrowserEventCommon :: (Ord n) => Vty.Event -> FileBrowser n -> EventM n (FileBrowser n)
 handleFileBrowserEventCommon e b =
     case e of
-        Vty.EvKey (Vty.KChar 'b') [Vty.MCtrl] -> do
-            let old = b ^. fileBrowserEntriesL
-            new <- listMovePageUp old
-            return $ b & fileBrowserEntriesL .~ new
-        Vty.EvKey (Vty.KChar 'f') [Vty.MCtrl] -> do
-            let old = b ^. fileBrowserEntriesL
-            new <- listMovePageDown old
-            return $ b & fileBrowserEntriesL .~ new
-        Vty.EvKey (Vty.KChar 'd') [Vty.MCtrl] -> do
-            let old = b ^. fileBrowserEntriesL
-            new <- listMoveByPages (0.5::Double) old
-            return $ b & fileBrowserEntriesL .~ new
-        Vty.EvKey (Vty.KChar 'u') [Vty.MCtrl] -> do
-            let old = b ^. fileBrowserEntriesL
-            new <- listMoveByPages (-0.5::Double) old
-            return $ b & fileBrowserEntriesL .~ new
+        Vty.EvKey (Vty.KChar 'b') [Vty.MCtrl] ->
+            actionFileBrowserListPageUp b
+        Vty.EvKey (Vty.KChar 'f') [Vty.MCtrl] ->
+            actionFileBrowserListPageDown b
+        Vty.EvKey (Vty.KChar 'd') [Vty.MCtrl] ->
+            actionFileBrowserListHalfPageDown b
+        Vty.EvKey (Vty.KChar 'u') [Vty.MCtrl] ->
+            actionFileBrowserListHalfPageUp b
         Vty.EvKey (Vty.KChar 'g') [] ->
-            return $ b & fileBrowserEntriesL %~ listMoveTo 0
-        Vty.EvKey (Vty.KChar 'G') [] -> do
-            let sz = length (listElements $ b^.fileBrowserEntriesL)
-            return $ b & fileBrowserEntriesL %~ listMoveTo (sz - 1)
+            actionFileBrowserListTop b
+        Vty.EvKey (Vty.KChar 'G') [] ->
+            actionFileBrowserListBottom b
         Vty.EvKey (Vty.KChar 'j') [] ->
-            return $ b & fileBrowserEntriesL %~ listMoveBy 1
+            actionFileBrowserListNext b
         Vty.EvKey (Vty.KChar 'k') [] ->
-            return $ b & fileBrowserEntriesL %~ listMoveBy (-1)
+            actionFileBrowserListPrev b
         Vty.EvKey (Vty.KChar 'n') [Vty.MCtrl] ->
-            return $ b & fileBrowserEntriesL %~ listMoveBy 1
+            actionFileBrowserListNext b
         Vty.EvKey (Vty.KChar 'p') [Vty.MCtrl] ->
-            return $ b & fileBrowserEntriesL %~ listMoveBy (-1)
+            actionFileBrowserListPrev b
         _ ->
             handleEventLensed b fileBrowserEntriesL handleListEvent e
 
@@ -715,14 +779,14 @@
 
     in withDefAttr fileBrowserAttr $
        vBox [ withDefAttr fileBrowserCurrentDirectoryAttr cwdHeader
-            , renderList (renderFileInfo foc maxFilenameLength (b^.fileBrowserSelectedFilesL))
+            , renderList (renderFileInfo foc maxFilenameLength (b^.fileBrowserSelectedFilesL) (b^.fileBrowserNameL))
                          foc (b^.fileBrowserEntriesL)
             , maybeSearchInfo
             , withDefAttr fileBrowserSelectionInfoAttr selInfo
             ]
 
-renderFileInfo :: Bool -> Int -> Set.Set String -> Bool -> FileInfo -> Widget n
-renderFileInfo foc maxLen selFiles listSel info =
+renderFileInfo :: Bool -> Int -> Set.Set String -> n -> Bool -> FileInfo -> Widget n
+renderFileInfo foc maxLen selFiles n listSel info =
     (if foc
      then (if listSel then forceAttr listSelectedFocusedAttr
                else if sel then forceAttr fileBrowserSelectedAttr else id)
@@ -734,6 +798,7 @@
         addAttr = maybe id (withDefAttr . attrForFileType) (fileInfoFileType info)
         body = addAttr (hLimit (maxLen + 1) $
                padRight Max $
+               (if foc && listSel then showCursor n (Location (0,0)) else id) $
                str $ fileInfoSanitizedFilename info <> suffix)
         suffix = (if fileInfoFileType info == Just Directory then "/" else "") <>
                  (if sel then "*" else "")
diff --git a/src/Brick/Widgets/List.hs b/src/Brick/Widgets/List.hs
--- a/src/Brick/Widgets/List.hs
+++ b/src/Brick/Widgets/List.hs
@@ -75,6 +75,7 @@
 
 import Prelude hiding (reverse, splitAt)
 
+import Control.Applicative ((<|>))
 #if !MIN_VERSION_base(4,8,0)
 import Control.Applicative ((<$>), (<*>), pure)
 import Data.Foldable (Foldable, find, toList)
@@ -574,10 +575,11 @@
                   -> GenericList n t e
 listMoveToElement e = listFindBy (== e) . set listSelectedL Nothing
 
--- | Set the selected index to the next element matching the
--- predicate.  If there is no selected element, the search starts at
--- the beginning.  If no matching element is found, leave the list
--- unmodified.
+-- | Starting from the currently-selected position, attempt to find
+-- and select the next element matching the predicate. If there are no
+-- matches for the remainder of the list or if the list has no selection
+-- at all, the search starts at the beginning. If no matching element is
+-- found anywhere in the list, leave the list unmodified.
 --
 -- /O(n)/.  Only evaluates as much of the container as needed.
 listFindBy :: (Foldable t, Splittable t)
@@ -586,9 +588,11 @@
            -> GenericList n t e
 listFindBy test l =
     let start = maybe 0 (+1) (l ^. listSelectedL)
-        (_, t) = splitAt start (l ^. listElementsL)
-        result = find (test . snd) . zip [0..] . toList $ t
-    in maybe id (set listSelectedL . Just . (start +) . fst) result l
+        (h, t) = splitAt start (l ^. listElementsL)
+        tailResult = find (test . snd) . zip [start..] . toList $ t
+        headResult = find (test . snd) . zip [0..] . toList $ h
+        result = tailResult <|> headResult
+    in maybe id (set listSelectedL . Just . fst) result l
 
 -- | Return a list's selected element, if any.
 --
