Creating a Twitter tweet box with Svelte

Today I want to go about creating a Twitter tweet box with Svelte. But before I start on this, I want to just point out some of the features that the official Twitter tweet box provides.

Features

  • Character count
  • Emoji selector
  • Image/Video upload
  • GIF selector
  • Poll creator
  • Location flag

We will not be attempting to build all of these features in our version, I am even inclined to say that our version like most versions you find online are not really Twitter tweet box clones. They are something completely different. So the only feature we will be adding to ours is the Character count feature, but lets look into this even more.

Character count feature

What normally happen with the tweet box is that, as the user types, the character count become less, as the count gets below 21 we notice that the character counter becomes a red colour to highlight we are running low on characters. Now as we continue typing and our character count gets below 0, we start to notice that the actual text we are typing in the textarea now has a red background behind each character below 0. The red background behind the text is also something we won’t be tackling in this tutorial. Now as you can see, we (nor any of the other tutorials out there) are not really re-creating the Twitter tweet box as the title of the tutorial stated.

What we are really creating is a textarea that has a character counter and some flags that will decide when the submit button become active or not.

I had to make the above very clear, because there are too many tutorials claiming to create something which they are not. Yes the title of this tutorial is kind of a click bait, but how else would I be able to convince you that other tutorials claiming this are lying?

Lets start building this thing

In order to get this up and running quickly I am not going to go about installing Svelte locally, if you need to do so please follow the instructions on their website. If this is the first time you are hearing about Svelte go and read my previous post on using Svelte. For this we can use the online REPL or this new online editor which has support for Svelte called StackBlitz. I will be making use of StackBlitz throughout this tutorial.

We can start off by creating our HTML and CSS in our single file component. But what do we need, we need an textarea, an button and a text placeholder of where out character counter will go.

<div class="tweet-box">
  <textarea class="tweet-input"></textarea>
  <button class="active">Tweet</button>
  <span class="danger">140</span>
</div>

This on its own doesn’t look like much by itself, I mean if you were to view that in a browser it wouldn’t look like much. Lets nice it up by adding some CSS.

<div class="tweet-box">
  <textarea class="tweet-input"></textarea>
  <button class="active">Tweet</button>
  <span class="danger">140</span>
</div>

<style>
  .tweet-box {
    font-size: 18px;
    padding: 10px;
    position: relative;
  }
  .tweet-input {
    border: 1px solid #ccc;
    border-radius: 10px;
    box-sizing: border-box;
    font-family: 'Times New Roman', Times, serif;
    font-size: 18px;
    padding: 15px 12px;
    outline: none;
    width: 100%;
  }
  span {
    float: right;
    padding: 18px 14px;
  }
  .warning {
    color: #a76c00;
  }
  .danger {
    color: #800000;
  }
  button {
    background-color: #ccc;
    border: 1px solid #eee;
    border-radius: 10px;
    color: #fefefe;
    font-size: 18px;
    float: right;
    margin-top: 10px;
    padding: 10px 25px;
  }
  button:hover {
    background-color: #333;
  }
  button.active {
    background-color: #008cff;
    cursor: pointer;
  }
</style>

You can see the results below.

Now we should have something looking more appealing. Yeah I know its definitely not the greatest design in the world, but its good enough to build our tweet box functionality on.

With Svelte some tasks are made easy because you are able to use JavaScript expressions inside of the template tags. So lets look at an easy way to test and see results without writing any interactive JavaScript code in a <script> tag.

JavaScript expressions are units of code that resolve to a value. You can read more about them here.

<div class="tweet-box">
  <textarea class="tweet-input" bind:value='message'></textarea>
  <button class="{{ message.length > 140 ? '' : 'active'}}" disabled="{{ message.length > 140 }}">Tweet</button>
  <span class="{{ message.length > 119 ? 'danger' : ''}}">{{ 140 - message.length }}</span>
</div>

<script>
  export default {
    data() {
      return {
        message: ''
      }
    }
  }
</script>

You can see the results below.

Now this will become hard to maintain over time, as you can see we are doing a lot of work inside the HTML code itself, what we should do is move most of this into the JavaScript blocks and figure out all of our computations there.

Lets move our button states into the JavaScript code as Svelte computed properties.

<div class="tweet-box">
  <textarea class="tweet-input" bind:value='message'></textarea>
  <button class="{{ messageCounter < 0 ? '' : 'active'}}" disabled="{{ messageCounter < 0 }}">Tweet</button>
  <span class="{{ messageCounter < 21 ? 'danger' : ''}}">{{ messageCounter }}</span>
</div>

<script>
  export default {
    data() {
      return {
        message: ''
      }
    },
    computed: {
      messageCounter: function (message) {
        return 140 - message.length;
      }
    }
  }
</script>

You can see the results below.

You can now see we are getting a clearer picture when we introduced one computed property, we can now move on to introducing more computed properties to almost rid the HTML code of all the JavaScript expressions.

<div class="tweet-box">
  <textarea class="tweet-input" bind:value='message'></textarea>
  <button class="{{ buttonState }}" disabled="{{ messageCounter < 0 }}">Tweet</button>
  <span class="{{ counterState }}">{{ messageCounter }}</span>
</div>

<script>
  export default {
    data: function () {
      return {
        message: ''
      }
    },
    computed: {
      messageCounter: function (message) {
        return 140 - message.length;
      },
      buttonState: function (messageCounter) {
        return messageCounter >= 0 && messageCounter <= 140 ? 'active' : '';
      },
      counterState: function (messageCounter) {
        if (messageCounter > 10 && messageCounter < 21) {
          return 'warning';
        } else if (messageCounter < 11) {
          return 'danger';
        }
        return '';
      }
    }
  }
</script>

You can see the results below.

What you will notice in some of the code is that we are calculating computed properties based upon another computed property. So this will ensure that when one computed property changes, it will trigger the others to change in an efficient way and it also stops us duplicating code like we were doing before when we had everything in the HTML template.