I Moved the Chat Mode Toggle to the Top of the Sidebar
Mode chips at the bottom of a sidebar stay invisible until you scroll. Moved to the top, with type and aria-pressed kept intact.
TL;DR
Moving the chat mode switch to the sidebar's top fixed a real usability bug: users only found the search-versus-about toggle after scrolling past the decision point. Core lesson: layout order is decision order, so mode-setting controls belong above scrollable content. The move kept accessibility attributes like aria-pressed and type=button intact, shrinking the form to one responsibility.
My chat page has two modes: searching articles, or asking about me. The problem: the switch between them was invisible. It sat attached to the input form at the bottom of the sidebar, so users had to scroll the whole chat history before realizing the choice existed. This commit moves it to the top of the sidebar, and it settles a small question I often skip: if a control decides what the page will do, it must be visible before the decision is made, not after.
My Wrong Guess: This Is Just Visual Taste
At first I treated the old placement as harmless, merely unattractive. The switch was two neat chips inside a frame, part of the input form, and forms do sit at the bottom of sidebars in many apps. I even considered simply giving the active state a stronger color.
What I missed: layout order is decision order. The mode changes what happens when the user types and sends, so choosing a mode happens before interacting with the form. With the picker at the bottom, users only discovered it after passing the decision point. This is not taste, it is the order of information. Once seen that way, only one placement makes sense: the very top, before anything that scrolls.
The Code That Moved, the Contract That Stayed
In ChatThreadPanel.tsx, both chips moved out of the form into a grid grid-cols-2 gap-1 p-3 at the top of the component, above the history bar. The button code barely changed, and that is the point:
<div className="grid grid-cols-2 gap-1 p-3 pb-2">
<button type="button" aria-pressed={mode === "search"}
onClick={() => onModeChange("search")}>...</button>
<button type="button" aria-pressed={mode === "about"}
onClick={() => onModeChange("about")}>...</button>
</div>
The type="button" attribute moved along untouched. Per the MDN documentation, a button without an explicit type inside a form behaves as a submit button [1]; the chips now live outside the form, so the risk is gone, but keeping the explicit type keeps them safe if they ever move back in, and the intent reads clearly in code. Same with aria-pressed, which marks a button as a toggle and announces its active state to screen readers [2]. Relocation is not a reason to strip a correct accessibility contract.
The active visual split stays two-flavored: an active search mode gets a solid mint background with black text, an active about mode gets a mint border with colored text. Solid contrast for the primary action, outline for the secondary, the same pattern as before, only now where it can be seen.
The Shrunken Form as Proof
My favorite side effect: the input form shrank to its own responsibility. No more mode-picker grid inside it; now it is exactly an plus a send button, separated by a thin top border. One block of UI, one responsibility. The mode switch is not part of the act of sending, so it has no reason to live inside a form element.
The pattern to borrow from this commit: open the screen with the control that sets the mode, let scrollable content sit below, and keep semantic attributes attached while moving elements around. Tailwind's grid-cols-2 handles the two equal columns without a single line of custom CSS [3]. A ten-minute change, but reading it changed how I will arrange the next sidebar.