Quick start
Every example on this page is a piece of one panel, a general store.
Creating and nesting components
Section titled “Creating and nesting components”A component is a function whose name starts with a capital letter and whose
return type is Component:
const ShopInfo = (): Component => ( <SunkenBox> <text x="center" y={12} w={{ fill: 16 }} h={48} font={Fonts.PLAIN} text="This shop restocks every few minutes. Selling to it lowers what it pays." align="center" shadow color={Colors.DIM} lineHeight={16} /> </SunkenBox>);Lowercase tags such as <text> are the seven built-in elements. Capitalised
tags such as <SunkenBox> are components, and you nest them the same way:
export const Shop = (): Component => ( <Window title="General Store"> <layer x={10} y={42} w={{ fill: 20 }} h={{ fill: 52 }}> <ShopTabs /> <ShopContents /> <CoinPurse /> </layer> </Window>);Window and SunkenBox are not part of ts2. They are ordinary components in
the project’s src/chrome.ts2.
Writing markup
Section titled “Writing markup”A component returns one element. Wrap siblings in a <layer>, or in <>…</>
if you want no container; return null to draw nothing. Close every tag, and
put expressions in braces:
<text x="right" y="center" w={60} h={16} text={`${price} gp`} font={Fonts.PLAIN} align="right" valign="middle" shadow color={Colors.AMBER} />Positioning and sizing
Section titled “Positioning and sizing”Every element takes x, y, w and h, as pixels or keywords:
<layer x={10} y={42} w={{ fill: 20 }} h={{ fill: 52 }}>x="left" | "center" | "right" | anchored to an edge of the parent, or centred |
y="top" | "center" | "bottom" | the same, vertically |
w="fill" / h="fill" | as large as the parent |
w="50%" | half the parent |
w={{ fill: 8 }} | the parent, less 8 pixels |
x={{ center: -20 }} | centred, then shifted 20 left |
Layout has the full set.
A component receives props through its one parameter, destructured and typed:
const StockRow = ({ item, name, price, stock }: { item: Item; name: String; price: Int; stock: Int;}): Component => ( <layer x={6} w={{ fill: 12 }} h={ROW_H}> … <sprite x={0} y="center" w={36} h={32} item={item} itemCount={-1} />You pass them like attributes:
<StockRow item={row.item} name={row.name} price={row.price} stock={row.stock} y={4 + i * ROW_H} />StockRow does not declare y, so it lands on the row’s root <layer>.
Conditional rendering
Section titled “Conditional rendering”Use && to draw something or nothing:
{stock > 0 && ( <text x={44} y={19} w={116} h={12} text={`${stock} in stock`} font={Fonts.SMALL} valign="middle" shadow color={Colors.DIM} />)}Use ? : to choose between two things. Either arm may be null:
{stock === 0 ? ( <text x="right" y="center" w={60} h={16} text="Sold out" font={Fonts.PLAIN} align="right" valign="middle" shadow color={Colors.RED} /> ) : ( <text x="right" y="center" w={60} h={16} text={`${price} gp`} font={Fonts.PLAIN} align="right" valign="middle" shadow color={Colors.AMBER} /> )}Write stock === 0, not !stock.
Rendering lists
Section titled “Rendering lists”A constant array can hold records, and each field keeps its type:
const STOCK = [ { item: Items.BRONZE_DAGGER, name: 'Bronze dagger', price: 12, stock: 30 }, { item: Items.IRON_SCIMITAR, name: 'Iron scimitar', price: 154, stock: 8 }, { item: Items.OAK_SHORTBOW, name: 'Oak shortbow', price: 80, stock: 0 }, { item: Items.ADAMANT_KITESHIELD, name: 'Adamant kiteshield', price: 5_440, stock: 2 },];.map() makes one component per entry:
const BuyList = (): Component => ( <SunkenBox> {STOCK.map((row, i) => ( <StockRow item={row.item} name={row.name} price={row.price} stock={row.stock} y={4 + i * ROW_H} /> ))} </SunkenBox>);.map() also runs over a list you declared or a table the game ships.
Rendering lists covers the three kinds.
Responding to events
Section titled “Responding to events”Pass a function to an event prop:
<layer x={i * 79} y={0} w={74} h={22} onClick={() => { setTab(i); }}>To make a component clickable, wrap it in a <layer> and put the handler
there.
Events lists every handler.
Game state
Section titled “Game state”Anything the server owns is read with a hook:
const CoinPurse = (): Component => { const inventory = useInventory(Inventories.INVENTORY); const coins = inventory.total(Items.COINS);Game variables use useServerState, skills useSkill.
Interface state
Section titled “Interface state”A cell holds state that is yours rather than the game’s, such as the open tab. Declare it at the top of the file when two components share it:
const openTab = sessionState(0);useState returns its value and a setter:
const ShopContents = (): Component => { const [tab] = useState(openTab);
return ( <layer x={0} y={28} w="fill" h={152}> {tab === 0 && <BuyList />} {tab === 1 && <SellList />} {tab === 2 && <ShopInfo />} </layer> );};A cell for one component is useState(0). sessionState lasts until logout;
persistedState is saved on the player’s device.
Interface state has the details.
Making it an interface
Section titled “Making it an interface”A <ui> returned from a function is what the game opens:
const shopRef = createRef('docs-shop');
export const shopUi = (): UI => ( <ui ref={shopRef} w={252} h={260}> <Shop /> </ui>);The createRef string is the interface’s name outside your code. w and h
take the layer keywords, measured against the box it opens into.
Interfaces and slots covers the
rest.
Next steps
Section titled “Next steps”Read the chapters in order from Writing markup, or look anything up in the Reference.