pug Template

The pug template literal lets you write JSX using Pug syntax. This is optional — CSSX works great with standard JSX.

In CSSX 0.3, Pug templates also support TypeScript syntax in TSX files and can contain a terminal embedded style block:

  • style(lang='styl') for Stylus
  • style for plain CSS

For components written in Pug, embedded styles are the preferred way to colocate local styles.

Basic Usage

import { pug } from 'cssxjs'
import { View, Text } from 'react-native'

function Card({ title, children }) {
  return pug`
    View.card
      Text.title= title
      View.content
        = children

    style(lang='styl')
      .card
        background white
        border-radius 8px
      .title
        font-size 18px
      .content
        padding 16px
  `
}

Why Pug?

Pug offers a concise, indentation-based syntax:

// JSX
<View styleName="container">
  <View styleName="header">
    <Text styleName="title">Hello</Text>
  </View>
</View>

// Pug
View.container
  View.header
    Text.title Hello

Embedded Style Blocks

Embedded style blocks are written as the last top-level node in a pug template.

Stylus

return pug`
  Pressable.button(onPress=onPress)
    Text.text= children

  style(lang='styl')
    .button
      padding 12px 16px
      border-radius 6px
      background #1677ff
    .text
      color white
`

CSS

return pug`
  View.card
    Text.title CSS syntax

  style
    .card {
      padding: 16px;
      border-radius: 8px;
      background: white;
    }

    .title {
      font-size: 18px;
    }
`

The transform moves the embedded style block into the immediate enclosing scope as a styl or css template literal. Standalone styl and css template literals remain supported for JSX components and shared module-level styles.

TypeScript

Pug templates in TSX files support TypeScript expressions. Use npx cssxjs check for Pug-aware type checks, because tsc --noEmit does not type-check expressions inside Pug template strings.

See TypeScript Support for the full guide.

Class Names

Class names (.className) become the styleName prop:

View.button.primary
// -> <View styleName="button primary" />

View.card.featured.large
// -> <View styleName="card featured large" />

Dynamic Classes

// Array with object for modifiers
View.button(styleName=[variant, { active, disabled }])
// -> <View styleName={['button', variant, { active, disabled }]} />

// Object syntax
View.button(styleName={ active, disabled })
// -> <View styleName={['button', { active, disabled }]} />

Attributes

Pass attributes in parentheses:

// Static
Pressable.btn(disabled)
  Text Submit

// Dynamic
TextInput.input(value=inputValue onChangeText=handleChange)

// Spread
View.wrapper(...props)

// Part attribute
View.root(part="root")

Content

Text Content

Text.title= title              // Variable
Text.text Hello World          // Static text
Text.count #{count} items      // Inline interpolation

Children

function Wrapper({ children }) {
  return pug`
    View.wrapper
      = children
  `
}

Control Flow

Conditionals

pug`
  View.container
    if isLoggedIn
      Text.user= userName
    else
      Pressable.login
        Text Login
`

Loops

pug`
  View.list
    each item in items
      View.item(key=item.id)
        Text= item.name
`

Components

Use PascalCase for components:

import { pug } from 'cssxjs'
import { View, Text } from 'react-native'
import { Card } from './Card'
import { Button } from './Button'

function App() {
  return pug`
    View.app
      Card.featured(title="Welcome")
        Text This is content
        Button.primary(onPress=handlePress) Click Me
  `
}

Complete Example

import { pug } from 'cssxjs'
import { View, Text, Image, Pressable } from 'react-native'

function UserProfile({ user, isOnline, onLogout }) {
  return pug`
    View.root(part="root")
      View.header(part="header")
        Image.avatar(source={ uri: user.avatar })
        View.info
          Text.name= user.name
          if isOnline
            Text.status.online Online
          else
            Text.status.offline Offline

      View.content(part="content")
        Text.bio= user.bio

      View.actions
        Pressable.button.secondary(onPress=onLogout)
          Text.buttonText Logout

    style(lang='styl')
      .root
        background white
        border-radius 12px

      .header
        flex-direction row
        align-items center
        gap 16px
        padding 20px

      .avatar
        width 64px
        height 64px
        border-radius 32px

      .name
        font-size 20px

      .status
        font-size 12px
        padding 2px 8px
        border-radius 10px
        overflow hidden
        &.online
          background #4caf50
          color white
        &.offline
          background #9e9e9e
          color white

      .content
        padding 20px

      .actions
        padding 16px 20px
        border-top-width 1px
        border-top-color #eee

      .button
        padding 8px 16px
        border-radius 6px
        &.secondary
          background #f5f5f5

      .buttonText
        color #333
  `
}

Mixing JSX and Pug

You can use both in the same file:

import { pug } from 'cssxjs'
import { View } from 'react-native'

function App({ children }) {
  // JSX for complex logic
  const header = (
    <View>
      <Navigation items={navItems} />
    </View>
  )

  // Pug for simpler structure
  return pug`
    View.app
      = header
      View.content
        = children
  `
}

Disabling Pug

If you don't use Pug, disable it for faster builds:

// babel.config.js
['cssxjs/babel', {
  transformPug: false
}]

Quick Reference

FeatureJSXPug
Class namesstyleName="a b".a.b
Attributesprop={value}(prop=value)
Text<Text>{text}</Text>Text= text
Conditionals{cond && <View />}if cond
Loops{items.map(...)}each item in items
Local Stylus stylesstyl templatestyle(lang='styl')
Local CSS stylescss templatestyle

See Also