# Counter example

> The smallest useful SymbioteNative app in React, Vue, Angular, Svelte, and Solid.

This example shows state, native layout, text, and a press event.

<Tabs syncKey="framework">
  <TabItem label="React">
    ```tsx
    import './App.css';
    import { useState } from 'react';

    export default function App() {
      const [count, setCount] = useState(0);

      return (
        <view className="root" onPress={() => setCount(value => value + 1)}>
          <text className="text">Count is {count}</text>
        </view>
      );
    }
    ```

    ```css
    /* App.css */
    .root { flex: 1; align-items: center; justify-content: center; }
    .text { font-size: 24px; }
    ```

  </TabItem>
  <TabItem label="Vue">
    ```vue
    <script setup lang="ts">
    import { ref } from 'vue';

    const count = ref(0);
    </script>

    <template>
      <view class="root" @press="count += 1">
        <text class="text">Count is {{ count }}</text>
      </view>
    </template>

    <style scoped>
    .root { flex: 1; align-items: center; justify-content: center; }
    .text { font-size: 24px; }
    </style>
    ```

  </TabItem>
  <TabItem label="Angular">
    ```ts
    import { Component } from '@angular/core';
    import { SYMBIOTE_ELEMENTS } from '@symbiote-native/angular';
    import './app.css';

    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [SYMBIOTE_ELEMENTS],
      template: `
        <view class="root">
          <pressable (press)="count = count + 1">
            <text class="text">Count is {{ count }}</text>
          </pressable>
        </view>
      `,
    })
    export class App {
      count = 0;
    }
    ```

    ```css
    /* app.css */
    .root { flex: 1; align-items: center; justify-content: center; }
    .text { font-size: 24px; }
    ```

  </TabItem>
  <TabItem label="Svelte">
    ```svelte
    <script lang="ts">
      let count = $state(0);
    </script>

    <!-- A press callback on a bare host tag goes through the `p={{ … }}` bag — an
         individual `onPress={…}` attribute is silently misrouted through Svelte's
         own DOM event system instead of the engine. -->
    <view class="root" p={{ onPress: () => (count += 1) }}>
      <text class="text">Count is {count}</text>
    </view>

    <style>
      .root { flex: 1; align-items: center; justify-content: center; }
      .text { font-size: 24px; }
    </style>
    ```

  </TabItem>
  <TabItem label="Solid">
    ```tsx
    import './App.css';
    import { createSignal } from 'solid-js';

    export function App() {
      const [count, setCount] = createSignal(0);

      return (
        <view class="root" onPress={() => setCount(value => value + 1)}>
          <text class="text">Count is {count()}</text>
        </view>
      );
    }
    ```

    ```css
    /* App.css */
    .root { flex: 1; align-items: center; justify-content: center; }
    .text { font-size: 24px; }
    ```

  </TabItem>
</Tabs>

## What changed

| Concern     | React                          | Vue                            | Angular                             | Svelte                                 | Solid                        |
| ----------- | ------------------------------ | ------------------------------- | ------------------------------------ | --------------------------------------- | ----------------------------- |
| State       | `useState()`                   | `ref()`                         | plain class field                    | `$state()` rune                         | `createSignal()`              |
| Press event | `onPress` directly on `view`   | `@press` directly on `view`     | `(press)` on a `pressable` tag       | `onPress` via a `p={{ onPress }}` bag   | `onPress` directly on `view`  |
| Styling     | `className="root"` + `App.css` | `class="root"` + SFC `<style>`  | `class="root"` + `app.css`           | `class="root"` + component `<style>`    | `class="root"` + `App.css`    |
| Native path | shared engine                  | shared engine                   | shared engine                        | shared engine                           | shared engine                 |

The `view` tag itself carries `onPress`/`onPressIn`/`onPressOut` — React, Vue,
and Solid's simplest counter reaches for it directly rather than wrapping in
`pressable`. Svelte needs the same handlers wrapped in a `p={{ … }}` bag
because an individual `on*` attribute on a bare host tag is intercepted by
Svelte's own compiled event system rather than reaching the engine directly.
Angular's counter uses the `pressable` tag here since its non-function-children
form is the idiomatic starting point on that adapter; either works on every
adapter (see the [Pressable example](/docs/examples/pressable/)).
`StyleSheet.create` works identically to a CSS class if you'd rather keep
style objects inline — see the [Styling guide](/docs/learn/styling/).
