The issue here is that setting the tabIndex of the TextButton doesn't set on the correct part of the dom - buttons are very complex to draw consistently with all decoration, icons, text, menus in a cross browser way.
At present, the tabIndex property is owned by the frame, which helps to draw these details. This is set up from within the ButtonCellDefaultAppearance class, when it invokes the follow line:
As currently implemented, this cannot be changed without a breaking change to the API. Two workarounds that I can initially suggest:
frame.render(sb, new Frame.FrameOptions(0, CommonStyles.get().noFocusOutline(), stylesBuilder.toSafeStyles()),
* Make a new appearance subclass, copying the existing appearance, but with a getter/setter for the tabIndex value, passing that in to the frame options constructor instead of zero. This is probably the 'most correct' solution, and will be the easiest to maintain - when that api changes in a minor release, you will get an error from java instead of it just no longer working
* Assign a real tab index in another element, such as the root of the button itself. This is more brittle, but easier to implement:
This workaround is from looking at the structure of the dom tree from a running app, and observing that the tabIndex is present on the first child (table) of the first child (div) of the root (div) of a button. This is confirmed from observing that the TextButton (actually, all CellComponents) has a <div> at its base, then the appearance kicks in. ButtonCellDefaultAppearance.render then adds an additional wrapper <div>, and the frame.render builds a table (see TableFrame's template and html file at com/sencha/gxt/theme/base/client/frame/TableFrame.html - this is the default, as can be seen in the ButtonCellDefaultAppearance constructor).
Thanks for reporting this, we'll provide further updates when we have a fix or a better workaround available.